@@ -1,6 +1,9 @@
# AGENTS.md — Project state, review findings, open items, and working rules
**Last updated: 2026-08-3 1** (Streckenberechnung: `wert_bei()` stat t `naechster_wert()` und
**Last updated: 2026-09-0 1** (Phantomfahrten beim Neu star t 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 i n
`.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 seine n
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: p ersist 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 ] P ersist 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.
### Off en: Befund 07, Erstzulassung
### Geschloss en: 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: