fb251154c7
Zwei getrennte Themen in einem Commit, beide in derselben Sitzung entstanden:
1. Ölwechsel/Inspektion wurden komplett ausgeblendet ("kein Eintrag im
Servicebuch"), sobald kein Servicebucheintrag vorlag - selbst wenn der
zugeordnete Sensor eine gültige Fälligkeit meldete. In beiden Frontends
prüfte die Anzeige nur den Servicebuch-Zweig, bevor sie die
Fahrzeugmeldung überhaupt las. Jetzt steht die Fahrzeugmeldung für sich;
fehlt zusätzlich ein Servicebucheintrag, übernimmt eine neue,
fahrtenlog-basierte Prognose (kmProTagAusFahrten()/meldungsPrognose())
die Hochrechnung statt der Servicebuch-Rate - deckelt auf die vom
Fahrzeug selbst gemeldete Zeitgrenze, falls zu wenig gefahren wird.
2. install.ps1 als Update-Weg wird von Windows Smart App Control blockiert,
ohne Umgehungsmöglichkeit. Die Integration lädt sich jetzt auf
Tastendruck selbst von Gitea (aktualisierung.py), verifiziert das
Manifest vor jedem Tausch und tauscht per os.rename mit automatischem
Rollback bei Fehlern - install.ps1 bleibt nur noch für die
Erstinstallation nötig. Zugangstoken über einen neuen OptionsFlow in
entry.options, nie in configuration.yaml.
Nebenbei: mehrere seit der HACS-Ausschluss-Entscheidung liegen gebliebene
falsche HACS-Referenzen in Code-Kommentaren und einem UI-Text korrigiert.
Details, Sicherheitsbegründung und Verifikationsstand in AGENTS.md,
Abschnitte I und J.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
"""Audi Dashboard - Fahrtenbuch, Tankstatistik und Fahrzeugzustand als eigene
|
|
Home-Assistant-Integration.
|
|
|
|
Diese Datei ist der Lebenszyklus: einrichten, entladen, neu laden. Die Fach-
|
|
logik liegt in den Modulen daneben, der laufende Zustand im Koordinator.
|
|
|
|
WAS DIE INTEGRATION MITBRINGT, WAS FRÜHER VON HAND EINGERICHTET WERDEN MUSSTE
|
|
-----------------------------------------------------------------------------
|
|
Die pyscript-Fassung brauchte fünf getrennte Handgriffe in der Installation:
|
|
einen `pyscript:`-Block mit allow_all_imports und hass_is_global, einen
|
|
`panel_custom:`-Block, das Kopieren der Oberflächen-Dateien nach /config/www/,
|
|
ein langlebiges Zugriffstoken in einer Textdatei und ein `pip install pypdf`
|
|
im Container. Jeder davon konnte vergessen werden, und mehrere scheiterten
|
|
still.
|
|
|
|
Alle fünf sind hier weg:
|
|
- Das Panel meldet die Integration selbst an (kein YAML, kein Neustart).
|
|
- Die Oberflächen-Dateien liegen in frontend/ und werden von hier unter
|
|
einem eigenen URL-Pfad ausgeliefert (kein Kopieren nach www/).
|
|
- Den Verlauf liest die Integration direkt über die recorder-API (kein
|
|
Token, siehe verlauf.py).
|
|
- pypdf steht über manifest.json bereit (kein pip-Aufruf, siehe belege.py).
|
|
- Eingerichtet wird über die Oberfläche (config_flow), nicht über YAML.
|
|
|
|
Was NICHT hierher gehört und deshalb weiterhin von Hand in die
|
|
configuration.yaml kommt: der `recorder:`-Block mit der verlängerten
|
|
Aufbewahrung (recorder_snippet.yaml). Der betrifft die ganze Instanz und ihre
|
|
Datenbankgröße - das ist eine Entscheidung des Betreibers, keine, die eine
|
|
einzelne Integration für ihn treffen darf.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
from homeassistant.components.http import StaticPathConfig
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import Platform
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.components import frontend, panel_custom
|
|
from homeassistant.loader import async_get_integration
|
|
|
|
from . import dienste
|
|
from .const import (
|
|
DOMAIN,
|
|
PANEL_ICON,
|
|
PANEL_KOMPONENTE,
|
|
PANEL_TITEL,
|
|
PANEL_URL,
|
|
STATIK_ORDNER,
|
|
STATIK_URL,
|
|
)
|
|
from .koordinator import Koordinator
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
PLATTFORMEN = [Platform.SENSOR]
|
|
|
|
type AudiConfigEntry = ConfigEntry[Koordinator]
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: AudiConfigEntry) -> bool:
|
|
integration = await async_get_integration(hass, DOMAIN)
|
|
version = str(integration.version)
|
|
|
|
koordinator = Koordinator(hass, entry, version)
|
|
entry.runtime_data = koordinator
|
|
|
|
await _statik_anmelden(hass)
|
|
await koordinator.starten()
|
|
await hass.config_entries.async_forward_entry_setups(entry, PLATTFORMEN)
|
|
await _panel_anmelden(hass, version)
|
|
dienste.registrieren(hass, koordinator)
|
|
|
|
# Ändert sich die Einrichtung, wird die Integration neu geladen statt
|
|
# halb angepasst - das ist die einzige Variante, bei der nichts von der
|
|
# alten Einrichtung übrig bleiben kann.
|
|
entry.async_on_unload(entry.add_update_listener(_neu_laden))
|
|
|
|
_LOGGER.info("Audi Dashboard %s eingerichtet", version)
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: AudiConfigEntry) -> bool:
|
|
geladen = await hass.config_entries.async_unload_platforms(entry, PLATTFORMEN)
|
|
if not geladen:
|
|
return False
|
|
dienste.abmelden(hass)
|
|
frontend.async_remove_panel(hass, PANEL_URL)
|
|
await entry.runtime_data.beenden()
|
|
return True
|
|
|
|
|
|
async def _neu_laden(hass: HomeAssistant, entry: AudiConfigEntry) -> None:
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
async def _statik_anmelden(hass: HomeAssistant) -> None:
|
|
"""Liefert frontend/ unter STATIK_URL aus.
|
|
|
|
Nur einmal je Home-Assistant-Lauf: aiohttp-Routen lassen sich nicht wieder
|
|
entfernen, ein zweiter Versuch nach einem Neu-Laden der Integration würde
|
|
scheitern. Das ist unkritisch - der Pfad zeigt auf einen Ordner, dessen
|
|
Inhalt sich beim Neu-Laden nicht ändert.
|
|
|
|
cache_headers=False ist Absicht: die Dateien werden von install.ps1
|
|
ersetzt, und ein Browser, der eine alte Fassung 31 Tage lang aus dem
|
|
Cache bedient (die HA-Vorgabe für statische Pfade), wäre genau die
|
|
stille Veraltung, die das Versions-Verfahren verhindern soll."""
|
|
if hass.data.get(DOMAIN, {}).get("statik"):
|
|
return
|
|
ordner = os.path.join(os.path.dirname(__file__), STATIK_ORDNER)
|
|
await hass.http.async_register_static_paths(
|
|
[StaticPathConfig(STATIK_URL, ordner, False)]
|
|
)
|
|
hass.data.setdefault(DOMAIN, {})["statik"] = True
|
|
|
|
|
|
async def _panel_anmelden(hass: HomeAssistant, version: str) -> None:
|
|
"""Meldet den Sidebar-Eintrag an.
|
|
|
|
Die Version hängt als Query-Parameter an der Modul-URL: sie ändert sich
|
|
genau dann, wenn eine neue Fassung installiert wurde, und ist damit der
|
|
passende Cache-Brecher für den Lade-Stub."""
|
|
await panel_custom.async_register_panel(
|
|
hass,
|
|
frontend_url_path=PANEL_URL,
|
|
webcomponent_name=PANEL_KOMPONENTE,
|
|
sidebar_title=PANEL_TITEL,
|
|
sidebar_icon=PANEL_ICON,
|
|
module_url=f"{STATIK_URL}/audi-dashboard-panel.js?v={version}",
|
|
embed_iframe=False,
|
|
require_admin=False,
|
|
config={"version": version, "statik": STATIK_URL},
|
|
)
|