d8b12da36d
Das Backend liegt jetzt als custom_components/audi_dashboard/ vor - eine normale Home-Assistant-Integration mit Config-Flow, einer sensor-Plattform und 18 Diensten. Damit ist die App über HACS installierbar; bis das Repo auf GitHub gespiegelt ist (HACS spricht ausschließlich mit GitHub), installiert homeassistant/installationspaket/install.ps1 denselben Ordner ohne HACS. Fünf Installationsschritte entfallen ersatzlos: der pyscript:-Block, der panel_custom:-Block, das Kopieren der Oberfläche nach www/, das langlebige Zugriffstoken (der Verlauf wird direkt über die recorder-API gelesen) und "pip install pypdf" (steht in manifest.json). Das Fahrzeugprofil legt die Integration beim ersten Start aus ihrer Vorlage an. Drei alte Schwächen sind dabei mit erledigt: - Die Nutzlast landet nicht mehr in der Recorder-Datenbank (_unrecorded_attributes - das kann nur eine echte Entität). - Eine laufende Fahrt überlebt einen Neustart (Store statt Arbeitsspeicher); fiel sie während eines Ausfalls ins Ende, schließt nach_neustart_fortsetzen() sie beim letzten aufgezeichneten Zeitpunkt. - Sensor-Zuordnungen wirken sofort - die Zustandsbeobachter werden neu gebunden, der Neustart-Hinweis und der Neustart-Dienst sind weg. Namensvertrag geändert, beide Oberflächen mitgezogen: pyscript.audi_dashboard_x -> sensor.audi_dashboard_x, pyscript.audi_dashboard_y -> audi_dashboard.y. Eine Companion-App vom alten Stand findet nach dem Umstieg nichts mehr und muss neu gebaut werden; das Panel liegt in der Integration und kann nicht driften. Der selbstgebaute Updater entfällt - HACS ist die Update-Mechanik, die Home Assistant kennt. Die Versionierung schrumpft auf eine Quelle: manifest.json. Geprüft am laufenden Testcontainer (Container byteweise identisch mit dem Repo): alle 18 Dienste, Panel, Config-Entry neu laden, Historienimport, echter Shell-Beleg in-process, Neuinstallation im Wegwerf-Container blank mit automatisch nachinstalliertem pypdf. Companion-App: tsc sauber, 112/112 Tests, beide Rauchtests gegen das laufende Backend grün. Belegparser 8/8. Co-Authored-By: Claude Opus 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 HACS 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},
|
|
)
|