Setup-Menü für Sensor-Zuordnung + Umstellung von WLAN/VAG-Integration auf FMM003

Setup-Menü (Einstellungen -> Fahrzeug einrichten -> Setup): ordnet alle von
der App genutzten Sensor-Rollen echten HA-Entitäten zu, statt sie in
einstellungen.py von Hand einzutragen - durchsuchbares Dropdown je Feld,
Vorauswahl aus vorhandenen Entitäten, Schalter für "nur passende Sensoren".
Neues Modul entitaeten.py (Katalog + JSON-Override, zur Laufzeit über
setattr() auf einstellungen angewendet, kein Neustart nötig außer für die
drei trigger-gebundenen Felder).

Zusätzlich: Fahrzeug-WLAN-Erkennung und alle Entity-IDs der nicht mehr
genutzten TommiG1/HA_VAG-EU-Data-Act-Integration entfernt. Fahrterkennung
läuft jetzt über den Zündungs-/ACC-Status des neu angebundenen Teltonika
FMM003 (ersetzt die WLAN-Verbindungserkennung); Standort und 12V-
Batteriespannung kommen ebenfalls vom FMM003. Kilometerstand, Tankfüllstand,
Reichweite, Türen/Fenster/Schlösser sowie Ölwechsel-/Inspektionsdaten haben
dadurch vorerst keine Quelle mehr und zeigen "unbekannt" - die Funktionen
selbst bleiben erhalten und lassen sich über das neue Setup-Menü jederzeit
neu zuordnen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-12 15:55:59 +02:00
co-authored by Claude Sonnet 5
parent 7e979ecfab
commit 9794193803
13 changed files with 848 additions and 197 deletions
+47 -44
View File
@@ -1,17 +1,25 @@
"""Fahrterkennung über die WLAN-Verbindung zum Fahrzeug (§7.1).
"""Fahrterkennung über den Zündungs-/ACC-Status des FMM003 (§7.1).
Start: der WLAN-Sensor der Companion App meldet den in den Einstellungen
hinterlegten Fahrzeug-WLAN-Namen (Fahrzeugprofil, Feld fahrzeug.wlan_name).
Ende: die Verbindung bricht ab und kommt binnen der Pausenzeit nicht zurück.
Start: die Zündungs-Entität (einstellungen.ZUENDUNG_SENSOR) wechselt auf
"on". Ende: sie wechselt auf "off" und kommt binnen der Pausenzeit nicht
zurück.
Die Pausenregel wird über task.unique() umgesetzt: jede neue Änderung des
WLAN-Sensors killt eine noch laufende, wartende Bestätigung des vorherigen
Ereignisses. Kommt die Verbindung während der Wartezeit zurück, wird dadurch
die wartende Ende-Bestätigung automatisch abgebrochen, ohne dass die Fahrt
beendet wird - das entspricht der Pausenregel aus §7.1.
2026-08-12: ersetzt die frühere WLAN-basierte Erkennung (Vergleich des vom
iPhone gemeldeten verbundenen WLANs gegen den im Fahrzeugprofil hinterlegten
Namen) - die Zündung ist ein direkteres, von jeder Companion-App-Eigenheit
unabhängiges Signal.
Die Pausenregel wird über task.unique() umgesetzt: jede neue Änderung der
Zündungs-Entität killt eine noch laufende, wartende Bestätigung des
vorherigen Ereignisses. Kommt die Zündung während der Wartezeit zurück, wird
dadurch die wartende Ende-Bestätigung automatisch abgebrochen, ohne dass die
Fahrt beendet wird - das entspricht der Pausenregel aus §7.1.
Die Entity-IDs stehen zentral in pyscript/modules/einstellungen.py - dort vor
der Installation einmalig anpassen (siehe INSTALL.md).
der Installation einmalig anpassen (siehe INSTALL.md), oder über das
Setup-Menü in der Oberfläche zuordnen (siehe entitaeten.py; Änderungen dort
wirken für DIESEN Trigger allerdings erst nach einem Neustart, da
@state_trigger die Entity-ID einmalig beim Modul-Laden einliest).
EINSCHRÄNKUNG: der Zustand einer laufenden Fahrt (_fahrt_start_ts) lebt nur
im Arbeitsspeicher dieses Skripts. Ein HA-Neustart mitten in einer Fahrt oder
@@ -32,11 +40,6 @@ import profil
_fahrt_start_ts = None
def _profil_wlan_name():
p = profil.profil_lesen()
return p.get("fahrzeug", {}).get("wlan_name")
def _pausenzeit_sekunden():
p = profil.profil_lesen()
return p.get("einstellungen", {}).get("fahrten_pausenzeit_min", 15) * 60
@@ -47,39 +50,39 @@ def beim_start():
profil.ordner_sicherstellen()
@state_trigger(f"{einstellungen.WLAN_SENSOR}")
def wlan_verbindung_geaendert(value=None, old_value=None):
# Killt eine noch laufende Ende-Bestätigung aus einer vorherigen
# Änderung - das ist der Mechanismus hinter der Pausenregel.
task.unique("audi_dashboard_fahrt_ende_pruefung")
# Ohne konfigurierte Zündungs-Entität keinen Trigger registrieren - eine
# leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes Verhalten
# (siehe zustand_oder_none()-Kommentar in frontend_veroeffentlichung.py zur
# Grundhaltung "kein geratener Wert"), hier stattdessen ganz weggelassen.
if einstellungen.ZUENDUNG_SENSOR:
@state_trigger(f"{einstellungen.ZUENDUNG_SENSOR}")
def zuendung_geaendert(value=None, old_value=None):
# Killt eine noch laufende Ende-Bestätigung aus einer vorherigen
# Änderung - das ist der Mechanismus hinter der Pausenregel.
task.unique("audi_dashboard_fahrt_ende_pruefung")
ziel = _profil_wlan_name()
if not ziel:
log.warning("audi_dashboard: kein fahrzeug.wlan_name im Fahrzeugprofil hinterlegt")
return
global _fahrt_start_ts
an_jetzt = (value == "on")
an_vorher = (old_value == "on")
global _fahrt_start_ts
verbunden_jetzt = (value == ziel)
verbunden_vorher = (old_value == ziel)
if an_jetzt and not an_vorher:
if _fahrt_start_ts is None:
_fahrt_start_ts = datetime.datetime.now(datetime.timezone.utc)
log.info(f"audi_dashboard: Fahrt gestartet um {_fahrt_start_ts.isoformat()}")
return
if verbunden_jetzt and not verbunden_vorher:
if _fahrt_start_ts is None:
_fahrt_start_ts = datetime.datetime.now(datetime.timezone.utc)
log.info(f"audi_dashboard: Fahrt gestartet um {_fahrt_start_ts.isoformat()}")
return
if an_vorher and not an_jetzt and _fahrt_start_ts is not None:
start_ts = _fahrt_start_ts
abbruch_ts = datetime.datetime.now(datetime.timezone.utc)
wartezeit_s = _pausenzeit_sekunden()
if verbunden_vorher and not verbunden_jetzt and _fahrt_start_ts is not None:
start_ts = _fahrt_start_ts
abbruch_ts = datetime.datetime.now(datetime.timezone.utc)
wartezeit_s = _pausenzeit_sekunden()
# task.unique() oben markiert diese Ausführung als die aktuell
# gültige. Kommt die Zündung vor Ablauf der Wartezeit zurück,
# ruft der nächste Trigger erneut task.unique() auf und killt
# genau diesen task.sleep() - der Code danach läuft dann nie.
task.sleep(wartezeit_s)
# task.unique() oben markiert diese Ausführung als die aktuell
# gültige. Kommt die Verbindung vor Ablauf der Wartezeit zurück,
# ruft der nächste Trigger erneut task.unique() auf und killt genau
# diesen task.sleep() - der Code danach läuft dann nie.
task.sleep(wartezeit_s)
_fahrt_beenden(start_ts, abbruch_ts)
_fahrt_beenden(start_ts, abbruch_ts)
def _fahrt_beenden(start_ts, ende_ts):
@@ -120,7 +123,7 @@ def _fahrt_beenden(start_ts, ende_ts):
@service
def audi_dashboard_fahrt_manuell_anlegen(ts_start=None, ts_end=None, art="privat"):
"""Manuelle Fahrterfassung aus der Oberfläche (§5.3), unabhängig von der
automatischen WLAN-Erkennung. Aufruf als pyscript.audi_dashboard_fahrt_manuell_anlegen."""
automatischen Zündungs-Erkennung. Aufruf als pyscript.audi_dashboard_fahrt_manuell_anlegen."""
start_ts = datetime.datetime.fromisoformat(ts_start)
ende_ts = datetime.datetime.fromisoformat(ts_end)
dauer_s = int((ende_ts - start_ts).total_seconds())