70253e83a5
Die Zündungs-Entität stammt vom FMM003-Tracker; verliert er unterwegs kurz die Verbindung, meldet Home Assistant "unavailable" statt eines echten Zündungszustands. Die Live-Erkennung wertete das bisher wie "aus" - eine Funklücke über der Pausenzeit teilte eine durchgehende Fahrt in zwei. zuendung_geaendert() ignoriert "unavailable"/"unknown" jetzt vollständig und leitet "fährt gerade" aus dem eigenen Zwischenstand (fahrt_start_ts) statt aus dem letzten Rohwert ab. historienimport.py bekommt zusätzlich eine Plausibilitätsprüfung: eine errechnete Durchschnittsgeschwindigkeit über 300 km/h deutet auf einen Kilometerstand-Ausreißer an der Fahrtgrenze hin, nicht auf eine echte Fahrt - die Strecke wird dann verworfen statt eine unmögliche Fahrt anzuzeigen. Tankerkennung: optionaler zweiter Sensor für das Tankvolumen in Litern (TANK_LITER_SENSOR, direkt vom CAN) neben dem bisherigen Prozent-Füllstand. Ist er zugeordnet, übernimmt er die automatische Tankerkennung vollständig - genauer als der Umweg über das im Fahrzeugprofil hinterlegte Tankvolumen, und der erkannte Anstieg liefert gleich eine grobe Vorbelegung für die getankte Menge statt eines leeren Feldes. Ohne den Sensor bleibt alles beim Alten. Der historische Import zieht mit derselben Präferenz nach. Manifest auf 2026.8.25.2, beide Änderungsrunden live im Testcontainer verifiziert (py_compile, Neustart, sauberes Setup-Log). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
237 lines
14 KiB
Python
237 lines
14 KiB
Python
"""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 und die
|
|
# Anzeige in der Übersicht.
|
|
TANK_SENSOR: str = ""
|
|
|
|
# Tankvolumen in Litern, direkt vom CAN (nicht aus dem Prozentwert
|
|
# umgerechnet). Optional - ist er gesetzt, übernimmt er die automatische
|
|
# Tankerkennung von TANK_SENSOR: ein Anstieg misst sich dann direkt in
|
|
# Litern statt über den Umweg Prozentpunkte -> Tankvolumen aus dem
|
|
# Fahrzeugprofil, und der erkannte Anstieg selbst dient als grobe
|
|
# Vorbelegung für die getankte Menge (siehe tankerkennung.py).
|
|
TANK_LITER_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 = ""
|
|
|
|
# Türschloss (getrennt von den Öffnungssensoren oben - zu heißt nicht
|
|
# verriegelt). Nur die Fahrertür: die Zentralverriegelung schließt immer
|
|
# alle Türen gemeinsam, ein einzelnes Türschloss lässt sich nicht separat
|
|
# bedienen - ihr Zustand gilt fürs ganze Fahrzeug (Nutzer-Entscheidung).
|
|
TUERSCHLOSS_SENSOR: str = ""
|
|
|
|
# Schiebedach (offen/zu) und Standlicht (an/aus) - ergänzen die
|
|
# Fenster-Gruppe bzw. stehen für sich, siehe _sicherheitscheck() in
|
|
# veroeffentlichung.py.
|
|
DACH_SENSOR: str = ""
|
|
LICHT_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": "TANK_LITER_SENSOR", "label": "Tankvolumen (Liter)", "gruppe": "fahrterkennung",
|
|
"hinweis": "Optional, vom CAN statt aus dem Prozentwert umgerechnet - macht die automatische "
|
|
"Tankerkennung genauer und liefert eine Vorbelegung für die getankte Menge.",
|
|
"domains": ["sensor"], "device_classes": ["volume"], "units": ["L", "l"], "liste": False, "pflicht": False,
|
|
"stichworte": ["tankvolumen", "liter", "litres", "fuel volume", "kraftstoffvolumen"]},
|
|
{"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": "TUERSCHLOSS_SENSOR", "label": "Türschloss (Fahrertür)", "gruppe": "sicherheit",
|
|
"hinweis": "\"aus\"/off = verriegelt. Die Zentralverriegelung schließt alle Türen gemeinsam - "
|
|
"die Fahrertür genügt für \"Fahrzeug verriegelt\".",
|
|
"domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False,
|
|
"pflicht": False, "stichworte": ["türschloss", "schloss", "lock", "verriegelt", "fahrertür"]},
|
|
{"key": "DACH_SENSOR", "label": "Schiebedach", "gruppe": "sicherheit",
|
|
"hinweis": "\"aus\"/off = zu.",
|
|
"domains": ["binary_sensor"], "device_classes": ["window", "door", "opening"], "units": [], "liste": False, "pflicht": False,
|
|
"stichworte": ["dach", "schiebedach", "sunroof", "roof"]},
|
|
{"key": "LICHT_SENSOR", "label": "Standlicht", "gruppe": "sicherheit",
|
|
"hinweis": "\"aus\"/off = kein Licht.",
|
|
"domains": ["binary_sensor"], "device_classes": ["light"], "units": [], "liste": False, "pflicht": False,
|
|
"stichworte": ["licht", "standlicht", "light", "parking lights"]},
|
|
{"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
|