Files
audi-app/custom_components/audi_dashboard/__init__.py
T
tobias d8b12da36d pyscript-Backend zur echten HA-Integration umgebaut (HACS-fähig)
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>
2026-08-23 23:53:56 +02:00

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},
)