From d1e3f7230c99fd7683da379abbe0115041af1c78 Mon Sep 17 00:00:00 2001 From: Tobi G Date: Tue, 1 Sep 2026 11:56:53 +0200 Subject: [PATCH] Parkbeginn nach einem Neustart aus der Aufzeichnung holen (2026.9.1.7) Der Zuhoerer bekommt den Zuendungswechsel nur mit, wenn Home Assistant laeuft. Ging die Zuendung aus, waehrend es unten war - ueber Nacht der Normalfall -, feuerte nichts, und die Registrierung beim Hochfahren wird bewusst ignoriert (sonst begaenne die Parkdauer bei jedem Neustart neu). Ergebnis war "Parkdauer unbekannt", obwohl der Zeitpunkt im recorder steht. _parkbeginn_nachholen() laeuft einmal in _nach_start und schlaegt ihn nach - nur, wenn kein Wert gespeichert ist und die Zuendung auf "aus" steht. Genommen wird der letzte echte Wechsel an -> aus im Fenster von sieben Tagen; gibt es keinen, bleibt es bei "unbekannt" statt den Rand des Fensters zu erfinden. Wieder mit der Meldezeit desselben Datensatzes statt seiner Ankunftszeit - dieselbe Begruendung wie in verlauf.geraetezeit(), hier rueckblickend. VERIFIZIERT in audi_ha_test, der Test trennt genau die beiden Zeiten: Speicher geleert, neu gestartet -> "Parkbeginn aus der Aufzeichnung nachgeholt: 09:04:57". Das ist die Geraetezeit; angekommen war der Datensatz um 09:44. Ein zweiter Neustart ruehrt den Wert nicht an, schreibt keine Zeile und erzeugt keinen Fehler. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 21 ++++- .../audi_dashboard/koordinator.py | 77 ++++++++++++++++++- .../audi_dashboard/manifest.json | 2 +- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 34baea9..6c3b32c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,7 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-09-01** („Geparkt seit" kommt von der Zündung, Manifest `2026.9.1.6`, +**Last updated: 2026-09-01** („Geparkt seit" kommt von der Zündung und wird nach einem Neustart +aus der Aufzeichnung nachgeholt, Manifest `2026.9.1.7`, Abschnitt BB. Davor: GNSS-Streckenrollen vorbereitet, `2026.9.1.5`, Abschnitt BA. Davor: Fahrtende ohne den 900-s-Nachlauf des Geräts, Manifest `2026.9.1.4`, siehe Abschnitt AZ. Davor am selben Tag: Fahrtbeginn und -ende kommen vom Gerät (`MELDEZEIT_SENSOR`), @@ -6629,6 +6630,24 @@ einer Gerätezeit von vor 40 Minuten → `09:04:57` statt `jetzt`. Companion-App gleichgezogen (Paritätsregel): `profilAdapter` um `geparktSeit` erweitert, `standortZustand()` braucht die Fahrtenliste nicht mehr. `tsc --noEmit` sauber, 165/165 Tests grün. +### Nachtrag (2026.9.1.7): der Parkbeginn überlebt jetzt auch einen Neustart über Nacht + +Der Zuhörer bekommt den Wechsel nur mit, wenn Home Assistant läuft. Ging die Zündung aus, während +es unten war — über Nacht der Normalfall —, feuerte nichts, und die Registrierung beim Hochfahren +wird ja bewusst ignoriert. Ergebnis: „Parkdauer unbekannt", obwohl der Zeitpunkt im recorder steht. + +`_parkbeginn_nachholen()` läuft einmal in `_nach_start` und schlägt ihn dort nach — **nur**, wenn +kein Wert gespeichert ist und die Zündung gerade auf „aus" steht. Genommen wird der letzte echte +Wechsel **an → aus** im Fenster von sieben Tagen; gibt es keinen, bleibt es bei „unbekannt", statt +den Rand des Fensters zu erfinden. + +Und wieder mit der **Meldezeit desselben Datensatzes**, nicht mit seiner Ankunftszeit — dieselbe +Begründung wie in `geraetezeit()`, hier nur rückblickend statt live. + +Live nachgewiesen, und der Test trennt genau die beiden Zeiten: Speicher geleert, neu gestartet → +`Parkbeginn aus der Aufzeichnung nachgeholt: 09:04:57`. Das ist die Gerätezeit; angekommen war der +Datensatz um 09:44. Ein zweiter Neustart rührt den Wert nicht an und schreibt keine Zeile. + ### Genauigkeit hängt am Operanden des Geräts Der Zeitpunkt ist nur so genau, wie das Gerät ihn meldet. Stand der Operand des diff --git a/custom_components/audi_dashboard/koordinator.py b/custom_components/audi_dashboard/koordinator.py index 87a8148..bc392de 100644 --- a/custom_components/audi_dashboard/koordinator.py +++ b/custom_components/audi_dashboard/koordinator.py @@ -76,11 +76,26 @@ from .const import ( E_ZUORDNUNG, SIGNAL_AKTUALISIERT, ) -from .veroeffentlichung import fahrzeugstatus, spannung_cache_vorladen +from .veroeffentlichung import ( + fahrzeugstatus, + spannung_cache_vorladen, + zustand_oder_none, +) from .zuordnung import Zuordnung _LOGGER = logging.getLogger(__name__) +# Wie weit zurück nach dem letzten Zündungswechsel gesucht wird, wenn der +# Parkbeginn nach einem Neustart fehlt. Sieben Tage decken jede realistische +# Standzeit ab und bleiben eine Abfrage, die der recorder in einem Rutsch +# beantwortet; weiter zurück reicht seine Aufbewahrung ohnehin selten. +PARKBEGINN_FENSTER = datetime.timedelta(days=7) + +# Wie weit um den Zündungswechsel herum nach der zugehörigen Meldezeit gesucht +# wird. Beide stammen aus demselben Datensatz und liegen deshalb +# Millisekunden auseinander (gemessen: 2 ms) - eine Minute ist reichlich. +MELDEZEIT_FENSTER = datetime.timedelta(minutes=1) + # Wie oft der Live-Fahrzeugstatus neu aus den Quellsensoren gelesen wird. TAKT_STATUS = datetime.timedelta(seconds=20) # Profil/Fahrten/Tankvorgänge ändern sich normalerweise nur durch Aktionen, @@ -160,6 +175,7 @@ class Koordinator: async def _nach_start(self, _hass: HomeAssistant) -> None: await fahrterkennung.nach_neustart_fortsetzen(self) + await self._parkbeginn_nachholen() # Erst hier (nicht in starten()): das Geräte-/Entity-Register der # zugeordneten Sensoren ist vor async_at_started oft noch nicht # vollständig - siehe die Begründung von trigger_neu_binden() oben. @@ -349,6 +365,65 @@ class Koordinator: self.fahrt_start_ts = ts await self._laufzeit_sichern() + async def _parkbeginn_nachholen(self) -> None: + """Holt den Parkbeginn aus der Aufzeichnung, wenn er fehlt. + + Der Zuhörer (_zuendung_angezeigt) bekommt den Wechsel nur mit, wenn Home + Assistant läuft. Ging die Zündung aus, während es unten war - über Nacht + der Normalfall -, feuert nichts; die Registrierung der Entität beim + Hochfahren wird bewusst ignoriert, sonst begänne die Parkdauer bei jedem + Neustart neu. Der Zeitpunkt steht aber im recorder, und dort wird er hier + einmalig nachgeschlagen. + + Nur wenn WIRKLICH ein Wechsel an -> aus im Fenster liegt. Steht die + Zündung schon die ganze Zeit auf aus, wissen wir den Beginn nicht - dann + bleibt es bei \"unbekannt\", statt den Rand des Fensters zu erfinden. Eine + falsche Zahl ist schlechter als keine (Ansage des Eigentümers, + 2026-09-01).""" + if self.geparkt_seit_ts is not None: + return + sensor = self.zuordnung.werte.ZUENDUNG_SENSOR + if not sensor or zustand_oder_none(self.hass, sensor) != "off": + return + + jetzt = datetime.datetime.now(datetime.UTC) + punkte = await verlauf.verlauf_lesen( + self.hass, sensor, jetzt - PARKBEGINN_FENSTER, jetzt + ) + # verlauf_lesen() filtert unknown/unavailable schon heraus - eine + # Funklücke trennt die Reihe also nicht in zwei Wechsel auf. + wechsel: datetime.datetime | None = None + vorher: str | None = None + for ts, wert in punkte: + if wert == "off" and vorher == "on": + wechsel = ts + vorher = wert + if wechsel is None: + return + + # Die Ankunftszeit des Datensatzes ist nicht seine Entstehungszeit. Die + # Meldezeit desselben Datensatzes ist es - dieselbe Begründung wie in + # verlauf.geraetezeit(), hier nur rückblickend statt live. + seit = wechsel + meldesensor = self.zuordnung.werte.MELDEZEIT_SENSOR + if meldesensor: + reihe = await verlauf.verlauf_lesen( + self.hass, meldesensor, wechsel - MELDEZEIT_FENSTER, wechsel + MELDEZEIT_FENSTER + ) + epoche = verlauf.naechster_wert(wechsel, reihe) + if epoche is not None: + try: + gemeldet = datetime.datetime.fromtimestamp(epoche, datetime.UTC) + except (ValueError, OSError, OverflowError): + gemeldet = None + if gemeldet is not None and gemeldet <= jetzt: + seit = gemeldet + + await self.geparkt_seit_setzen(seit) + _LOGGER.info( + "Parkbeginn aus der Aufzeichnung nachgeholt: %s", seit.isoformat() + ) + async def geparkt_seit_setzen(self, ts: datetime.datetime | None) -> None: self.geparkt_seit_ts = ts await self._laufzeit_sichern() diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index b5a6b1c..4a7e788 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.9.1.6", + "version": "2026.9.1.7", "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"],