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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# AGENTS.md — Project state, review findings, open items, and working rules
|
# 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.
|
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`,
|
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`),
|
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,
|
Companion-App gleichgezogen (Paritätsregel): `profilAdapter` um `geparktSeit` erweitert,
|
||||||
`standortZustand()` braucht die Fahrtenliste nicht mehr. `tsc --noEmit` sauber, 165/165 Tests grün.
|
`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
|
### Genauigkeit hängt am Operanden des Geräts
|
||||||
|
|
||||||
Der Zeitpunkt ist nur so genau, wie das Gerät ihn meldet. Stand der Operand des
|
Der Zeitpunkt ist nur so genau, wie das Gerät ihn meldet. Stand der Operand des
|
||||||
|
|||||||
@@ -76,11 +76,26 @@ from .const import (
|
|||||||
E_ZUORDNUNG,
|
E_ZUORDNUNG,
|
||||||
SIGNAL_AKTUALISIERT,
|
SIGNAL_AKTUALISIERT,
|
||||||
)
|
)
|
||||||
from .veroeffentlichung import fahrzeugstatus, spannung_cache_vorladen
|
from .veroeffentlichung import (
|
||||||
|
fahrzeugstatus,
|
||||||
|
spannung_cache_vorladen,
|
||||||
|
zustand_oder_none,
|
||||||
|
)
|
||||||
from .zuordnung import Zuordnung
|
from .zuordnung import Zuordnung
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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.
|
# Wie oft der Live-Fahrzeugstatus neu aus den Quellsensoren gelesen wird.
|
||||||
TAKT_STATUS = datetime.timedelta(seconds=20)
|
TAKT_STATUS = datetime.timedelta(seconds=20)
|
||||||
# Profil/Fahrten/Tankvorgänge ändern sich normalerweise nur durch Aktionen,
|
# Profil/Fahrten/Tankvorgänge ändern sich normalerweise nur durch Aktionen,
|
||||||
@@ -160,6 +175,7 @@ class Koordinator:
|
|||||||
|
|
||||||
async def _nach_start(self, _hass: HomeAssistant) -> None:
|
async def _nach_start(self, _hass: HomeAssistant) -> None:
|
||||||
await fahrterkennung.nach_neustart_fortsetzen(self)
|
await fahrterkennung.nach_neustart_fortsetzen(self)
|
||||||
|
await self._parkbeginn_nachholen()
|
||||||
# Erst hier (nicht in starten()): das Geräte-/Entity-Register der
|
# Erst hier (nicht in starten()): das Geräte-/Entity-Register der
|
||||||
# zugeordneten Sensoren ist vor async_at_started oft noch nicht
|
# zugeordneten Sensoren ist vor async_at_started oft noch nicht
|
||||||
# vollständig - siehe die Begründung von trigger_neu_binden() oben.
|
# vollständig - siehe die Begründung von trigger_neu_binden() oben.
|
||||||
@@ -349,6 +365,65 @@ class Koordinator:
|
|||||||
self.fahrt_start_ts = ts
|
self.fahrt_start_ts = ts
|
||||||
await self._laufzeit_sichern()
|
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:
|
async def geparkt_seit_setzen(self, ts: datetime.datetime | None) -> None:
|
||||||
self.geparkt_seit_ts = ts
|
self.geparkt_seit_ts = ts
|
||||||
await self._laufzeit_sichern()
|
await self._laufzeit_sichern()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "audi_dashboard",
|
"domain": "audi_dashboard",
|
||||||
"name": "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",
|
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||||
"codeowners": ["@paul"],
|
"codeowners": ["@paul"],
|
||||||
|
|||||||
Reference in New Issue
Block a user