From ff5a334e95bc943d6a7abdfb8cf783af8a9024eb Mon Sep 17 00:00:00 2001 From: Tobi G Date: Tue, 1 Sep 2026 10:04:55 +0200 Subject: [PATCH] Keine Phantomfahrt beim Neustart, Tankstand auf wert_bei() (2026.9.1.1) zuendung_geaendert() prueste den neuen Zustand, aber nicht den alten. Beim Hochfahren taucht die Zuendungs-Entitaet neu auf; das kommt als Wechsel mit old_state = None an. Steht das Trip-Signal ohnehin auf "an" - beim FMM003 der Normalfall -, las der Beobachter das als "Zuendung ging gerade an" und legte eine Fahrt an, deren Beginn schlicht der Zeitpunkt des Neustarts war. Daher der Eintrag 30.08. 19:43 bis 31.08. 08:37, 12,9 h, 0 km. Die Pruefung ist "if alt is None: return" - dieselbe, die _kilometerstand_geaendert() im Koordinator seit jeher hat. Eine wirklich laufende Fahrt geht nicht verloren: deren Beginn liegt im Store und wird von nach_neustart_fortsetzen() aufgenommen. Nachgewiesen: zwei Neustarts hintereinander bei trip_status = on und leerem Zwischenstand, null "Fahrt gestartet"-Zeilen und fahrt_start_ts bleibt null. Vorher entstand unter genau diesen Bedingungen jedes Mal eine. _verbrauch_screenen() liest die Literstaende jetzt ebenfalls mit wert_bei() statt naechster_wert() - dieselbe Doppelbewertung gegenueber historienimport.py wie beim Kilometerstand, eine Ebene tiefer. naechster_wert() bleibt bei Breiten-/Laengengrad: eine Position ist kein Zaehler. Auf Anweisung des Eigentuemers ausserdem die zwoelf Fahrten mit distance_km == 0 geloescht (ueber den Dienst fahrt_loeschen, nach einem backup_jetzt) und "Open items" in AGENTS.md aufgeraeumt: vier Karteileichen des MQTT-Wegs entfernt, Cloudflare-Zugang und iPhone-Signatur als erledigt gebucht, Befund 07 als Nutzereingabe geschlossen, QR-Scanner auf den halben Umfang gekuerzt. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 133 +++++++++++------- .../audi_dashboard/fahrterkennung.py | 17 +++ .../audi_dashboard/manifest.json | 2 +- custom_components/audi_dashboard/screening.py | 7 +- 4 files changed, 108 insertions(+), 51 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bca6fe4..3111414 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,9 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-08-31** (Streckenberechnung: `wert_bei()` statt `naechster_wert()` und +**Last updated: 2026-09-01** (Phantomfahrten beim Neustart abgestellt, Tankstand auf `wert_bei()` +nachgezogen, zwölf Nullfahrten gelöscht, „Open items" von vier MQTT-Karteileichen befreit; +Manifest `2026.9.1.1`, siehe Abschnitt AX. Davor am 2026-08-31: Streckenberechnung: `wert_bei()` +statt `naechster_wert()` und Rückwärtssprünge verworfen, Manifest `2026.8.31.6`, siehe Abschnitt AW. Davor am selben Tag: Audit über Panel und iOS-App: sieben Befunde, sechs behoben, `ZUENDUNG_SENSOR` auf das Trip-Signal des FMM003 umgelegt; Manifest `2026.8.31.5`, siehe @@ -439,15 +442,26 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed - [x] Offline UX per design brief (offline marker, visible pending queue) - [ ] **QR-Code-Scanner fürs Token-Onboarding (angefragt 2026-08-28, noch nicht geplant/gebaut)** — 2026-08-11 bewusst weggelassen ("nur wenn es einfach bleibt"), der Owner will das jetzt doch als - Alternative zum manuellen Einfügen. Ursprünglicher Plan aus `UMSETZUNGSPLAN.md` Phase 10 Stufe 2, - Schritt 6: Barcode-Plugin (`@capacitor-mlkit/barcode-scanning`) + Kamera-Berechtigung in der App, - Gegenstück eine kleine statische Seite unter HA `/local/dm360-qr.html`, die den Token **clientseitig** - (Offline-JS-QR-Bibliothek, keine Netzabfrage) als QR anzeigt — Inhalt `{"url": "...", "token": "..."}`. - Noch nicht scoped/umgesetzt. + Alternative zum manuellen Einfügen. -### B) Infrastructure / commissioning (partly waits for FMM003 hardware) + **Umfang halbiert am 2026-09-01:** Home Assistant zeigt den Token beim Anlegen bereits selbst + als QR-Code an. Die geplante eigene Seite unter `/local/dm360-qr.html` entfällt damit ersatzlos — + gebraucht wird nur noch die **Scanner-Seite in der App** (Barcode-Plugin + `@capacitor-mlkit/barcode-scanning` + Kamera-Berechtigung). Offen bleibt eine Frage, die vor dem + Bauen zu klären ist: HAs eigener QR trägt nur den Token, nicht die Instanz-URL — die muss also + weiterhin von Hand kommen oder aus dem Netz gefunden werden. -- [ ] Switch `datametric360.de` nameservers at all-inkl to Cloudflare ("full setup") — +### B) Infrastructure / commissioning + +> **Aufgeräumt am 2026-09-01.** Vier Einträge sind hier ersatzlos entfallen, weil der +> MQTT-/Mosquitto-Weg seit dem Wechsel auf flespi (2026-08-13) nicht mehr existiert: FMM003 +> verkabeln, Broker-Erreichbarkeit, MQTT Client Type, erste Codec-JSON aufzeichnen. Das Gerät +> läuft seit dem 13.08. produktiv über flespi; die Feldzuordnung steht und ist gemessen (siehe +> Abschnitt AU). + +- [x] Switch `datametric360.de` nameservers at all-inkl to Cloudflare ("full setup") — **erledigt, + vom Eigentümer bestätigt am 2026-09-01: Domain liegt bei Cloudflare, Nameserver stehen, die + iOS-App ist darüber angebunden.** prerequisite for the tunnel; domain carries nothing else, so this is consequence-free. Step-by- step runbook for this and everything below it now exists: [`homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md`](homeassistant/INTERNET_ZUGRIFF_EINRICHTEN.md) @@ -463,43 +477,11 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed 2026-08-28: corrected the stale `/local/dm360/*` assumption (never built) to the app's real remote need, its OTA bundle at `/audi_dashboard_static/app/*` (`@capgo/capacitor-updater`). See `homeassistant/REVERSE_PROXY.md`. -- [ ] Install/wire the FMM003; record firmware version (Codec JSON is firmware-dependent) - [x] Generate TLS certificates for Mosquitto + device (small private CA) — done 2026-08-11, 10-year validity; Mosquitto configured (`certfile`/`keyfile`/`cafile`/`require_certificate: true`). - Remaining: upload root/client cert/key to the FMM003 Security tab — filenames must end in - `.pem`/`.pem.crt`/`.pem.key` (Configurator rejects plain `.crt`/`.key`, content-agnostic check) -- [ ] Decide broker reachability for the vehicle — **reopened 2026-08-11 evening**: port-forward - 8883 on the Speedport Smart 4 Plus looked correctly configured (rule present, right internal - IP, right port) and internal reachability was confirmed (`homeassistant.local:8883` open from - the LAN, Mosquitto TLS listener genuinely up, TLS cert chain end-to-end verified byte-for-byte - against the FMM003's uploaded client cert), but the port stayed **closed from outside** - (confirmed via external port checker, both before and after a router reboot that changed the - dynamic WAN IP — DNS/DuckDNS matched correctly each time, so not a DNS or CGNAT issue). - Community reports (ComputerBase, Telekom Hilft) describe this as a known, Telekom-acknowledged - firmware bug on this router model; a full disable-the-firewall workaround doesn't exist on this - model either. Port-forward rule has been removed again. **New direction: route the FMM003 - through flespi instead** (native Teltonika/Codec8 channel, IMEI-based auth, no certs, no - inbound port needed at all — flespi has a stable public endpoint; HA pulls data back out via - flespi's REST API or MQTT, outbound-only). Free flespi tier (10 devices/2 channels) is enough - for one vehicle. Next step: user creates the flespi account + Teltonika channel; a `flespi` - custom integration is already present in this HA instance (unconfigured) — check what it - needs once flespi-side setup exists. - Unrelated but still valid from the same session: DuckDNS hostname - `datametric360.duckdns.org` reliably updating; Let's Encrypt for HA's own local UI works (root - cause of the earlier DNS-01 failures was a stray `aliases` entry in the DuckDNS add-on config, - not DNS/network — see COMPANION_APP_ARCHITECTURE.md §5 item 2a). HA's SSL config now lives in - Settings → System → Network (UI), not `configuration.yaml`'s old `http:` block, which was - removed after HA started migrating/ignoring it. The Mosquitto TLS setup itself (cert chain, - `require_certificate: true`, `certfile`/`keyfile`/`cafile`) is verified correct and can be - reused as-is if a self-hosted broker is ever revisited. -- [ ] MQTT Client Type on the FMM003 ("Custom server" not selectable in practice, "AWS IoT Custom" - pointed at a self-hosted broker was confirmed working by two independent community reports) — - **moot for now** given the flespi pivot above: flespi uses the device's native Codec8/TCP - channel, not MQTT at all, so Server Settings should switch to Protocol: TCP against the flespi - channel host/port instead, and Codec set to "Codec 8 Extended". Revisit this item only if a - self-hosted broker is picked back up later. -- [ ] Capture the first real Codec JSON message (`mosquitto_sub`/MQTT Explorer) and build the - field mapping from it — **do not guess beforehand** (explicit decision) + **Gegenstandslos seit dem Wechsel auf flespi (2026-08-13):** das Gerät spricht dort seinen + nativen Codec-8-Kanal über TCP, ohne Zertifikate. Der Mosquitto-Aufbau bleibt nur als + Bauanleitung stehen, falls je ein eigener Broker zurückkommt. - [x] Move trip detection to FMM003 ignition — done 2026-08-12. `fahrterkennung.py` rewritten: trigger is now `einstellungen.ZUENDUNG_SENSOR` (`binary_sensor.testzone_fmm003_engine_ignition_or_acc_status`, on = trip running) instead @@ -1730,9 +1712,12 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed 2026-08-11 (`UMSETZUNGSPLAN.md` Phase 2): swipe-delete keyboard fallback, popup keyboard access, and self-hosting Leaflet stay unfixed in this panel and are addressed only in DataMetric360. -- [ ] Optional: persist the RAM-only states (trip start, fuel low-water-mark) — deliberately - deferred; may become moot with the FMM003 switch -- [ ] Upload vehicle photos to `www/bilder/`, set `steuer.faellig` (operational data, not code) +- [x] Persist the RAM-only states (trip start, fuel low-water-mark) — erledigt mit dem Umbau zur + echten Integration: beide liegen im Laufzeit-Store des Koordinators + (`…_laufzeit`: `fahrt_start_ts`, `tiefststand_pct`, `tiefststand_liter`) und überleben einen + Neustart. Am 2026-09-01 am laufenden Container nachgesehen. +- [x] Fahrzeugfoto hochladen — erledigt, über die App (`seitenansicht.webp`, 2026-08-31) +- [ ] `steuer.faellig` setzen (Betriebsdaten, kein Code) — steht noch auf `None` ### D) Once the panel is superseded @@ -6277,12 +6262,14 @@ aus deutschen Kommentaren sehen wie Klassen aus, und `ads-dot--${status}` wird z Laufzeit zusammengesetzt und taucht in keiner Suche auf — sechs Design-System-Klassen standen deshalb zunächst falsch auf der Liste und wurden vor dem Melden aussortiert. -### Offen: Befund 07, Erstzulassung +### Geschlossen: Befund 07, Erstzulassung `fahrzeug.erstzulassung` ist leer, deshalb steht bei der Hauptuntersuchung in beiden Oberflächen „kein Eintrag im Wartungsplan" — ohne dieses Datum gibt es keinen Anker, -weder aus dem Wartungsplan noch aus einer Fahrzeugmeldung. Kein Codefehler; einzutragen -unter Einstellungen → Fahrzeug einrichten. Bewusst nicht erfunden. +weder aus dem Wartungsplan noch aus einer Fahrzeugmeldung. Kein Codefehler. + +**Am 2026-09-01 vom Eigentümer geschlossen:** das ist eine Eingabe des App-Nutzers unter +Einstellungen → Fahrzeug einrichten, kein offener Punkt des Projekts. Nicht wieder aufmachen. ### Was am Panel und an der App in Ordnung war @@ -6358,6 +6345,54 @@ in der Praxis beantwortet sie der Code bereits, nur mit der falschen Antwort. (`screening.py:246`), während der Import dort `wert_bei()` nimmt. Dieselbe Doppelung wie oben, eine Ebene tiefer. Bewusst außerhalb des Auftrags gelassen. +## AX. Phantomfahrten beim Neustart, und der Tankstand zieht nach (2026.9.1.1) + +### Jeder Neustart legte eine Fahrt an + +`zuendung_geaendert()` prüfte den neuen Zustand, aber nicht den alten. Beim Hochfahren von +Home Assistant taucht die Zündungs-Entität neu auf; das kommt als Zustandswechsel mit +`old_state = None` an. Steht das Trip-Signal ohnehin auf „an" — beim FMM003 der Normalfall, +solange die Zündung nicht sauber aus war —, las der Beobachter das als „Zündung ging gerade an" +und legte eine Fahrt an, deren **Beginn schlicht der Zeitpunkt des Neustarts** war. + +Daher der Eintrag `30.08. 19:43 → 31.08. 08:37, 12,9 h, 0 km`, und daher die Fahrt, die am +31.08. um 21:18:45 UTC beim Ausliefern von `2026.8.31.6` entstand. + +Die Prüfung ist `if alt is None: return` — dieselbe, die `_kilometerstand_geaendert()` im +Koordinator seit jeher hat. Eine wirklich laufende Fahrt geht dabei nicht verloren: deren Beginn +liegt im Store und wird von `nach_neustart_fortsetzen()` aufgenommen. + +Nachgewiesen: zwei Neustarts hintereinander bei `trip_status = on` und leerem Zwischenstand, +**null** `Fahrt gestartet`-Zeilen im Protokoll und `fahrt_start_ts` bleibt `null`. Vorher +entstand unter genau diesen Bedingungen jedes Mal eine. + +**Was das nicht löst, und der Eigentümer ausdrücklich will:** einen Beginn und ein Ende, die +*aus den Daten des Dongles* stammen. Heute ist der Beginn `datetime.now()` im Moment, in dem +Home Assistant den Wechsel verarbeitet — gemessen 0,5 bis 139 s nach dem Zeitstempel des Geräts, +nach einer Funklücke Minuten. Und ein Wechsel, den es nie gab, wird nie erkannt: die echte Fahrt +vom 31.08., 22:03–22:13 Ortszeit, hat die Live-Erkennung nicht gesehen, weil `trip_status` +schon seit 18:39 auf „an" stand. Eigenes Arbeitspaket, hängt an der Konfiguration des Geräts. + +### Der Tankstand liest jetzt wie der Kilometerstand + +`_verbrauch_screenen()` holte die Literstände weiter mit `naechster_wert()`, während +`historienimport.py` `wert_bei()` nimmt — dieselbe Doppelbewertung wie in Abschnitt AW, eine +Ebene tiefer. Der zeitlich nächstgelegene Stand zum Fahrtbeginn kann schon in der Fahrt liegen +und hätte dann einen Teil des Verbrauchs vorweggenommen. + +`naechster_wert()` bleibt an einer Stelle richtig und steht dort weiter: Breiten- und +Längengrad in `_position_screenen()` sind keine Zähler. + +### Nullfahrten entfernt + +Auf Anweisung des Eigentümers („0 km Fahrten sind keine Fahrten und damit zu entfernen") wurden +am 2026-09-01 die zwölf Fahrten mit `distance_km == 0` gelöscht — über den eigenen Dienst +`fahrt_loeschen`, nicht durch Hantieren an der Datei, und nach einem `backup_jetzt` +(`backups/20260901_072541`). Bestand danach: 11 Fahrten, keine mit 0 km. + +Das ist eine Abkehr von der Linie in Abschnitt W und AV, wo Nullfahrten bewusst stehen blieben. +Sie gilt, weil der Eigentümer sie ausdrücklich getroffen hat — nicht als neue Gewohnheit. + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/custom_components/audi_dashboard/fahrterkennung.py b/custom_components/audi_dashboard/fahrterkennung.py index 85015f8..e083b7f 100644 --- a/custom_components/audi_dashboard/fahrterkennung.py +++ b/custom_components/audi_dashboard/fahrterkennung.py @@ -91,6 +91,23 @@ async def zuendung_geaendert(k: Koordinator, neu: str | None, alt: str | None) - if neu not in ("on", "off"): return + # Kein Vorzustand heißt: die Entität taucht gerade erst auf - beim + # Hochfahren von Home Assistant oder nach einem Neuladen der Integration. + # Das ist kein Wechsel der Zündung, sondern eine Registrierung. + # + # Ohne diese Prüfung las der Beobachter das als "Zündung ging gerade an" + # und legte eine Fahrt an, deren Beginn schlicht der Zeitpunkt des + # Neustarts war. Steht das Trip-Signal ohnehin auf "an", entstand so bei + # JEDEM Neustart eine Phantomfahrt - daher der Eintrag vom 30.08.2026, + # 19:43 bis 08:37 des Folgetags, 12,9 Stunden, 0 km. + # + # Eine wirklich laufende Fahrt geht dabei nicht verloren: deren Beginn + # liegt im Store und wird von nach_neustart_fortsetzen() aufgenommen. + # _kilometerstand_geaendert() im Koordinator prüft aus demselben Grund + # schon immer auf einen Vorzustand. + if alt is None: + return + # Eine noch wartende Ende-Bestätigung aus einer vorherigen Änderung # abbrechen - das ist der Mechanismus hinter der Pausenregel. k.warte_ende_ab_abbrechen() diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index 0268e18..6054d09 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -1,7 +1,7 @@ { "domain": "audi_dashboard", "name": "Audi Dashboard", - "version": "2026.8.31.6", + "version": "2026.9.1.1", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "codeowners": ["@paul"], diff --git a/custom_components/audi_dashboard/screening.py b/custom_components/audi_dashboard/screening.py index 3d3d31b..187538c 100644 --- a/custom_components/audi_dashboard/screening.py +++ b/custom_components/audi_dashboard/screening.py @@ -281,8 +281,13 @@ async def _verbrauch_screenen( if not verlauf: return + # wert_bei() aus demselben Grund wie beim Kilometerstand (siehe Modulkopf): + # der Tankstand zu Fahrtbeginn ist der zuletzt DAVOR gemeldete, nicht der + # zeitlich nächstgelegene - der läge womöglich schon in der Fahrt und hätte + # einen Teil des Verbrauchs vorweggenommen. historienimport.py liest ihn + # seit jeher so. verbrauch = verbrauch_aus_literstaenden( - naechster_wert(start, verlauf), naechster_wert(ende, verlauf), fahrt.get("distance_km") + wert_bei(verlauf, start), wert_bei(verlauf, ende), fahrt.get("distance_km") ) if verbrauch is not None: await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], {"verbrauch_l_100km": verbrauch})