"""Welche Home-Assistant-Entität welche Rolle im Fahrzeug spielt. Das ist die einzige technische Konfiguration der App. Alles andere (Pausen- zeit, Reifendaten, Versicherung, ...) gehört ins Fahrzeugprofil und ist über die Oberfläche änderbar. **Im Auslieferstand ist hier alles leer.** Das ist Absicht, kein unfertiger Zustand: welche Entity-IDs richtig sind, hängt an der jeweiligen Instanz und ihren Integrationen. Zugeordnet wird nach der Installation im Setup-Menü der App (Einstellungen -> Fahrzeug einrichten -> Setup), das die Zuordnung nach audi_dashboard/entitaeten.json schreibt (siehe zuordnung.py). Ein leeres Feld ist der sichere Zustand: die betroffene Kachel zeigt "unbekannt" statt eines falschen Werts (siehe zustand_oder_none() in veroeffentlichung.py), und die trigger-gebundenen Felder registrieren gar keinen Zustandsbeobachter, statt einen gegen eine nicht existierende Entität zu registrieren. Eine gesetzte, aber falsche Entity-ID ist deshalb schlechter als eine leere - genau das ist 2026-08-23 einmal passiert, als hier noch die Entity-IDs einer längst abgeräumten Testinstanz standen. Zwei typische Quellen auf dieser Instanz: der Teltonika FMM003 (GPS-Tracker mit CAN-Anbindung, über flespi angebunden) und eine EU-Data-Act-Integration des Herstellers. Welche davon welche Rolle bedient, entscheidet das Setup-Menü - nicht diese Datei. UNTERSCHIED ZUR PYSCRIPT-FASSUNG: dort waren das Modul-Variablen, die entitaeten.py zur Laufzeit per setattr() auf dem importierten Modul-Objekt überschrieb. Das funktionierte, war aber ein globaler, veränderlicher Zustand quer durch alle Dateien. Hier ist es ein ganz normales Objekt, das der Koordinator hält und weiterreicht. """ from __future__ import annotations from dataclasses import dataclass, field, fields # Vier gleich aufgebaute Positionen für Türen und Fenster. POSITIONEN = ["vorne links", "vorne rechts", "hinten links", "hinten rechts"] @dataclass class Sensorzuordnung: """Die wirksame Zuordnung. Feldnamen = Schlüssel in entitaeten.json.""" # Fahrterkennung: Start/Ende einer Fahrt über den Zündungs-/ACC-Status # (on = Fahrt läuft). Dieselbe Entität speist auch die Anzeige # "fährt/steht" - damit können Erfassung und Anzeige nicht auseinander- # laufen. ZUENDUNG_SENSOR: str = "" # Kilometerstand - für Fahrtabschluss-Screening, Reifenzähler und # Ölwechsel-Prognose. # # Beim FMM003 hier NICHT den selbst berechneten Gesamtkilometerstand # (*_total_calculated_mileage) zuordnen: der beruht auf GPS-Strecken- # rechnung statt auf dem Tacho und damit auf einer anderen Zählbasis als # der echte Fahrzeug-Kilometerstand. Wer ihn einträgt, verfälscht alle # drei genannten Auswertungen mit einem inkonsistenten Basiswert. Der vom # CAN gelesene Wert (*_total_vehicle_mileage_read_from_can) bzw. der # Kilometerstand der EU-Data-Act-Integration ist der richtige. KM_SENSOR: str = "" # Tankfüllstand in Prozent - für die automatische Tankerkennung. TANK_SENSOR: str = "" # Reichweite (Übersicht). RANGE_SENSOR: str = "" # 12V-Batteriespannung. Beim FMM003 ist das external_power_voltage - die # vom Gerät gemessene Bordnetzspannung des Fahrzeugs -, NICHT # battery_voltage (das ist die interne Pufferbatterie des Trackers selbst # und hat mit der Fahrzeugbatterie nichts zu tun). BATTERIE_SENSOR: str = "" # Knopf für eine sofortige Neuabfrage beim Fahrzeug. REFRESH_BUTTON: str = "" # Türen/Fenster (je vier Positionen, siehe POSITIONEN). TUER_SENSOREN: list[str] = field(default_factory=list) FENSTER_SENSOREN: list[str] = field(default_factory=list) HECKKLAPPE_SENSOR: str = "" HAUBE_SENSOR: str = "" # Vom Fahrzeug selbst gemeldete Service-Fälligkeit (ergänzt die # App-eigene, aus dem Servicebuch berechnete Prognose). NAECHSTER_OELWECHSEL_SENSOR: str = "" OELWECHSEL_STRECKE_SENSOR: str = "" NAECHSTE_INSPEKTION_SENSOR: str = "" INSPEKTION_STRECKE_SENSOR: str = "" # Live-GPS-Position (Übersicht -> Standort-Kachel). Breiten-/Längengrad # als zwei eigene sensor-Entities: flespi liefert Koordinaten so, nicht # als Attribute einer device_tracker-Entity. STANDORT_LAT_SENSOR: str = "" STANDORT_LON_SENSOR: str = "" def _standardwerte() -> dict[str, object]: """Die eingebauten Werte, einmal eingefroren. Gebraucht für "Zurücksetzen" im Setup-Menü: die Zuordnung wird bei jedem Anwenden vollständig neu gesetzt, ein weggelassener Override fällt damit auf genau diesen Wert zurück.""" leer = Sensorzuordnung() werte: dict[str, object] = {} for f in fields(leer): wert = getattr(leer, f.name) werte[f.name] = list(wert) if isinstance(wert, list) else wert return werte STANDARDWERTE: dict[str, object] = _standardwerte() SCHLUESSEL: set[str] = set(STANDARDWERTE) # Katalog aller Rollen fürs Setup-Menü im Frontend. Reihenfolge = # Anzeige-Reihenfolge innerhalb jeder Gruppe. domains/device_classes/units # sind Erwartungen fürs Frontend-seitige Filtern und Vorschlagen (Schalter # "Nur passende Sensoren anzeigen") - eine fehlende oder leere Liste bedeutet # "keine Einschränkung" bzw. "diese Rolle hat üblicherweise keine Einheit". FELDER: list[dict] = [ {"key": "ZUENDUNG_SENSOR", "label": "Zündung/ACC-Status", "gruppe": "fahrterkennung", "hinweis": "on = Fahrt läuft, erkennt Fahrtbeginn/-ende.", "domains": ["binary_sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": True, "stichworte": ["zündung", "ignition", "acc", "motor", "engine"]}, {"key": "KM_SENSOR", "label": "Kilometerstand", "gruppe": "fahrterkennung", "hinweis": "Gesamt-Kilometerstand des Fahrzeugs - für Fahrtabschluss-Screening, Reifenzähler und Ölwechsel-Prognose.", "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, "stichworte": ["kilometer", "km", "mileage", "odometer", "tacho", "laufleistung"]}, {"key": "TANK_SENSOR", "label": "Tankfüllstand", "gruppe": "fahrterkennung", "hinweis": "Füllstand in Prozent - für die automatische Tankerkennung.", "domains": ["sensor"], "device_classes": [], "units": ["%"], "liste": False, "pflicht": False, "stichworte": ["tank", "fuel", "kraftstoff", "füllstand", "level"]}, {"key": "RANGE_SENSOR", "label": "Reichweite", "gruppe": "uebersicht", "hinweis": "Für die Übersicht - bleibt sie leer, zeigt die Oberfläche \"unbekannt\".", "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, "stichworte": ["reichweite", "range"]}, {"key": "BATTERIE_SENSOR", "label": "12V-Batteriespannung", "gruppe": "uebersicht", "hinweis": "Nicht jede Integration liefert diesen Wert - bleibt er leer, zeigt \"Mein Audi\" \"unbekannt\".", "domains": ["sensor"], "device_classes": ["voltage"], "units": ["V"], "liste": False, "pflicht": False, "stichworte": ["batterie", "battery", "spannung", "volt", "12v"]}, {"key": "REFRESH_BUTTON", "label": "Sofort-Aktualisierung (Knopf)", "gruppe": "uebersicht", "hinweis": "Der von der Integration bereitgestellte Knopf für \"Ziehen zum Aktualisieren\".", "domains": ["button"], "device_classes": [], "units": [], "liste": False, "pflicht": False, "stichworte": ["refresh", "aktualisieren", "update", "abrufen", "poll"]}, {"key": "TUER_SENSOREN", "label": "Türen", "gruppe": "sicherheit", "hinweis": "\"aus\"/off = zu. Für \"Sicher abgestellt\" wichtig.", "domains": ["binary_sensor"], "device_classes": ["door"], "units": [], "liste": True, "positionen": POSITIONEN, "pflicht": False, "stichworte": ["tür", "door"]}, {"key": "FENSTER_SENSOREN", "label": "Fenster", "gruppe": "sicherheit", "hinweis": "\"aus\"/off = zu.", "domains": ["binary_sensor"], "device_classes": ["window"], "units": [], "liste": True, "positionen": POSITIONEN, "pflicht": False, "stichworte": ["fenster", "window"]}, {"key": "HECKKLAPPE_SENSOR", "label": "Heckklappe", "gruppe": "sicherheit", "hinweis": "\"aus\"/off = zu.", "domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False, "stichworte": ["heckklappe", "tailgate", "kofferraum", "trunk"]}, {"key": "HAUBE_SENSOR", "label": "Motorhaube", "gruppe": "sicherheit", "hinweis": "\"aus\"/off = zu.", "domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False, "stichworte": ["haube", "hood", "bonnet", "motorhaube"]}, {"key": "NAECHSTER_OELWECHSEL_SENSOR", "label": "Nächster Ölwechsel (Datum)", "gruppe": "uebersicht", "hinweis": "Vom Fahrzeug selbst gemeldete Fälligkeit, ergänzt die App-eigene Servicebuch-Prognose.", "domains": ["sensor"], "device_classes": ["date", "timestamp"], "units": [], "liste": False, "pflicht": False, "stichworte": ["ölwechsel", "oil", "service", "change"]}, {"key": "OELWECHSEL_STRECKE_SENSOR", "label": "Nächster Ölwechsel (Restkilometer)", "gruppe": "uebersicht", "hinweis": "Meldet die Integration oft als negativen Countdown - die App zeigt nur den Betrag.", "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, "stichworte": ["ölwechsel", "oil", "distance", "strecke"]}, {"key": "NAECHSTE_INSPEKTION_SENSOR", "label": "Nächste Inspektion (Datum)", "gruppe": "uebersicht", "hinweis": "Vom Fahrzeug selbst gemeldete Fälligkeit.", "domains": ["sensor"], "device_classes": ["date", "timestamp"], "units": [], "liste": False, "pflicht": False, "stichworte": ["inspektion", "inspection", "service"]}, {"key": "INSPEKTION_STRECKE_SENSOR", "label": "Nächste Inspektion (Restkilometer)", "gruppe": "uebersicht", "hinweis": "Meldet die Integration oft als negativen Countdown - die App zeigt nur den Betrag.", "domains": ["sensor"], "device_classes": ["distance"], "units": ["km", "mi"], "liste": False, "pflicht": False, "stichworte": ["inspektion", "inspection", "distance", "strecke"]}, {"key": "STANDORT_LAT_SENSOR", "label": "GPS-Standort: Breitengrad", "gruppe": "standort", "hinweis": "Breitengrad als eigener Sensor (z. B. flespi).", "domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False, "stichworte": ["breitengrad", "latitude", "lat", "standort", "gps", "position", "ortung"]}, {"key": "STANDORT_LON_SENSOR", "label": "GPS-Standort: Längengrad", "gruppe": "standort", "hinweis": "Längengrad als eigener Sensor (z. B. flespi).", "domains": ["sensor"], "device_classes": [], "units": [], "liste": False, "pflicht": False, "stichworte": ["längengrad", "longitude", "lon", "lng", "standort", "gps", "position", "ortung"]}, ] # In der pyscript-Fassung stand hier eine Liste TRIGGER_GEBUNDEN: die drei # Felder ZUENDUNG/KM/TANK_SENSOR waren beim Modul-Laden einmalig in die # @state_trigger-Registrierung eingebacken, eine Änderung wirkte deshalb erst # nach einem HA-Neustart, und das Setup-Menü musste genau davor warnen. # Diese Integration bindet ihre Zustandsbeobachter bei jeder Änderung neu # (koordinator.trigger_neu_binden) - die Einschränkung, der Warnhinweis und # der eigens dafür gebaute Neustart-Dienst sind damit ersatzlos weg. for _feld in FELDER: _feld["trigger_gebunden"] = False