Files
audi-app/custom_components/audi_dashboard/zuordnung.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

77 lines
3.4 KiB
Python

"""Sensor-zu-Entität-Zuordnung fürs Setup-Menü (Einstellungen -> Fahrzeug
einrichten -> Setup).
audi_dashboard/entitaeten.json enthält nur die vom Nutzer über das Setup-Menü
geänderten Zuordnungen, keyed nach den Feldnamen aus einstellungen.py.
anwenden() legt sie über die eingebauten Standardwerte.
Wichtig ist, dass JEDES bekannte Feld bei jedem Durchlauf gesetzt wird, nicht
nur die belegten. Eine frühere Fassung übersprang leere Werte - weil die
Zuordnung aber ein dauerhaftes Objekt ist, blieb ein einmal gesetzter Wert
danach für immer stehen. "Zurücksetzen" im Setup-Menü hatte bei allen Feldern
mit leerem Standardwert (15 von 17) schlicht keine Wirkung, und die Oberfläche
zeigte beim nächsten Zeichnen wieder den alten Wert, als sei das Speichern
fehlgeschlagen. Umgekehrt wurde eine Liste aus leeren Einträgen gesetzt statt
übersprungen, was den Sicherheitscheck mit zwölf "unbekannt"-Zeilen füllte und
"Sicher abgestellt" dauerhaft grau werden ließ.
Jedes Feld bei jedem Durchlauf zu schreiben ist damit nicht nur korrekt,
sondern auch idempotent: der Zustand hängt allein an entitaeten.json, nicht an
der Reihenfolge vorheriger Aufrufe.
"""
from __future__ import annotations
from . import einstellungen
from .ablage import Ablage
from .einstellungen import FELDER, SCHLUESSEL, STANDARDWERTE, Sensorzuordnung
def _ist_leer(wert: object) -> bool:
"""Leer heißt: keine Zuordnung. Auch eine Liste, die nur leere Einträge
enthält - das Setup-Menü schickt für unbelegte Positionen ["","",...], und
das ist inhaltlich dasselbe wie gar keine Liste."""
if wert in (None, ""):
return True
if isinstance(wert, list):
return not any(eintrag for eintrag in wert)
return False
class Zuordnung:
"""Hält die wirksame Sensorzuordnung und pflegt sie aus entitaeten.json."""
def __init__(self, ablage: Ablage) -> None:
self._ablage = ablage
self.werte = Sensorzuordnung()
async def anwenden(self) -> None:
"""Setzt für jedes bekannte Feld den wirksamen Wert - den Override,
wenn einer hinterlegt ist, sonst den eingebauten Standardwert."""
overrides = await self._ablage.zuordnung_lesen()
for key in SCHLUESSEL:
wert = overrides.get(key)
standard = STANDARDWERTE[key]
gesetzt = standard if _ist_leer(wert) else wert
setattr(self.werte, key, list(gesetzt) if isinstance(gesetzt, list) else gesetzt)
async def speichern(self, mapping: dict) -> None:
"""Schreibt nur bekannte Schlüssel; unbekannte werden stillschweigend
verworfen, damit ein späterer Katalog-Umbau keine Datenleichen in
entitaeten.json hinterlässt."""
bereinigt = {k: v for k, v in mapping.items() if k in SCHLUESSEL}
await self._ablage.zuordnung_schreiben(bereinigt)
await self.anwenden()
def aktueller_stand(self) -> dict:
"""Katalog + aktuell wirksamer Wert je Feld, Grundlage für die Entität
sensor.audi_dashboard_entitaeten. Die Werte kommen aus dem
Zuordnungsobjekt selbst, nicht direkt aus der JSON-Datei - damit zeigt
die Oberfläche bereits angewendete Overrides und eingebaute
Standardwerte einheitlich."""
werte = {feld["key"]: getattr(self.werte, feld["key"], None) for feld in FELDER}
return {"katalog": FELDER, "werte": werte, "standardwerte": STANDARDWERTE}
__all__ = ["Zuordnung", "einstellungen"]