Meldezeit auch nach hinten prüfen - "Geparkt seit 20698 Tg." war die Unix-Epoche
geraetezeit() prüfte nur, ob die Meldezeit in der Zukunft liegt. Ein Sensor, der 0 meldet, rutschte durch, und fromtimestamp(0) ergibt 1970 - auf der realen Instanz sichtbar als "Geparkt seit 20698 Tg. 9 Std. 39 Min.". Neu geraetezeit_plausibel(): nicht aus der Zukunft und nicht älter als MELDEZEIT_RUECKLAUF (365 Tage). Geprüft wird an drei Stellen - beim Auslesen der Gerätezeit, beim Setzen des Parkbeginns und beim LADEN aus dem Laufzeit-Store, denn ein einmal falsch gespeicherter Wert überlebt sonst jedes Update. Fängt auch eine im Setup falsch zugeordnete ID-Entität ab: das flespi-Gerät hat drei ID-Sensoren, deren Namen dem Zeitstempel ähneln, und deren Werte ergeben als Unix-Zeit ebenfalls 1970. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +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-02** (Null heisst unbekannt, Momentanwerte nur fuer
|
**Last updated: 2026-09-02** (Null heisst unbekannt, Momentanwerte nur fuer
|
||||||
"jetzt", Uebersicht-Symbol vergroessert, `2026.9.2.5`, Abschnitt BN.
|
"jetzt", Uebersicht-Symbol vergroessert, `2026.9.2.7`, Abschnitte BN und BO.
|
||||||
Davor: Verbrauchsfaktor aus dem Tankbeleg, `2026.9.1.32`,
|
Davor: Verbrauchsfaktor aus dem Tankbeleg, `2026.9.1.32`,
|
||||||
Abschnitt BM. Davor: Reifenzaehler ohne Fahrzeugwechsel, „Raeder“
|
Abschnitt BM. Davor: Reifenzaehler ohne Fahrzeugwechsel, „Raeder“
|
||||||
statt „Reifen“, `2026.9.1.29`, Abschnitt BL. Davor: Ortsnamen aus dem Backend, Verbrauchseichung an
|
statt „Reifen“, `2026.9.1.29`, Abschnitt BL. Davor: Ortsnamen aus dem Backend, Verbrauchseichung an
|
||||||
@@ -7554,6 +7554,56 @@ halb so hoch wie breit. Echte Hoehengleichheit mit den Nachbarn gibt es nur
|
|||||||
durch Neuzeichnen des Bogens mit groesserem Winkel - dem Eigentuemer angeboten,
|
durch Neuzeichnen des Bogens mit groesserem Winkel - dem Eigentuemer angeboten,
|
||||||
nicht unaufgefordert gemacht.
|
nicht unaufgefordert gemacht.
|
||||||
|
|
||||||
|
## BO. "Geparkt seit 20698 Tg." - die Meldezeit war nur nach vorn geprueft (2026.9.2.7)
|
||||||
|
|
||||||
|
Gemeldet von der realen Instanz. Nachgerechnet: 20698 Tage und 9:39 vor dem
|
||||||
|
Ablesezeitpunkt ergeben den Sekundenwert **0** - die Unix-Epoche.
|
||||||
|
|
||||||
|
`geraetezeit()` prueft seit Abschnitt AY, ob die Meldezeit in der ZUKUNFT
|
||||||
|
liegt (falsch gestellte Geraeteuhr). Nach hinten gab es keine Grenze. Ein
|
||||||
|
Sensor, der 0 meldet, rutschte damit ungehindert durch, und
|
||||||
|
`fromtimestamp(0)` ergibt 1970.
|
||||||
|
|
||||||
|
### Warum "der Dongle liefert nicht" die falsche Erklaerung war
|
||||||
|
|
||||||
|
Auf die Rueckfrage des Eigentuemers nachgesehen: im Testcontainer steht
|
||||||
|
`message_timestamp` auf 1788342429, also exakt jetzt. Beide Instanzen haengen
|
||||||
|
an derselben flespi-Quelle - am Geraet kann es also nicht liegen.
|
||||||
|
|
||||||
|
Die wahrscheinlichere Ursache ist die Zuordnung. Das flespi-Geraet hat FUENF
|
||||||
|
aehnlich klingende Sensoren, aber nur zwei sind Zeitstempel:
|
||||||
|
|
||||||
|
| Entitaet | Wert | tauglich |
|
||||||
|
|---|---|---|
|
||||||
|
| `…_message_timestamp` | 1788342429 | **ja - die richtige** |
|
||||||
|
| `…_timestamp_when_server_received_a_message` | 1788342433 | nein, Ankunftszeit |
|
||||||
|
| `…_id_of_channel_that_received_a_message` | 1421491 | nein, eine ID |
|
||||||
|
| `…_id_of_device_that_received_a_message` | 8767678 | nein, eine ID |
|
||||||
|
| `…_id_of_device_type_of_device…` | 1304 | nein, eine ID |
|
||||||
|
|
||||||
|
Eine faelschlich zugeordnete ID ergibt als Unix-Zeit ebenfalls einen Zeitpunkt
|
||||||
|
kurz nach 1970 - dieselbe Anzeige, andere Ursache. Beim Eigentuemer noch
|
||||||
|
offen, welcher der beiden Faelle vorliegt.
|
||||||
|
|
||||||
|
### Der Riegel sitzt an drei Stellen, nicht an einer
|
||||||
|
|
||||||
|
1. `geraetezeit_plausibel()` (neu in `verlauf.py`): nicht aus der Zukunft
|
||||||
|
(`MELDEZEIT_VORLAUF`, 5 min) und nicht aelter als `MELDEZEIT_RUECKLAUF`.
|
||||||
|
2. `geparkt_seit_setzen()` prueft mit - so kommt ein solcher Wert gar nicht
|
||||||
|
erst in den Laufzeit-Store.
|
||||||
|
3. **Beim LADEN**, und das ist der Teil, der die reale Instanz betrifft: der
|
||||||
|
bereits gespeicherte Epochenwert haette jedes Update ueberlebt. Geprueft
|
||||||
|
wurde dort nur, ob sich das Datum lesen laesst, und
|
||||||
|
`_parkbeginn_nachholen()` springt nicht ein, solange etwas gespeichert ist.
|
||||||
|
|
||||||
|
`MELDEZEIT_RUECKLAUF = 365 Tage` (Vorgabe des Eigentuemers, zuerst 30). Sehr
|
||||||
|
weit gefasst und genau deshalb ungefaehrlich: das Geraet puffert real nur
|
||||||
|
Stunden bis Tage, die Grenze kann also keine echte gepufferte Meldung
|
||||||
|
verwerfen - was sie fangen soll, liegt Jahrzehnte daneben.
|
||||||
|
|
||||||
|
An acht Faellen geprueft: Epoche, beide ID-Verwechslungen und 366 Tage
|
||||||
|
verworfen; jetzt, 2 Tage und 364 Tage angenommen.
|
||||||
|
|
||||||
## Working conventions (observed — keep them)
|
## Working conventions (observed — keep them)
|
||||||
|
|
||||||
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
|
- German is the project language: identifiers, comments, commits, UI texts. Exceptions:
|
||||||
|
|||||||
@@ -353,9 +353,23 @@ class Koordinator:
|
|||||||
roh = gespeichert.get("geparkt_seit_ts")
|
roh = gespeichert.get("geparkt_seit_ts")
|
||||||
if roh:
|
if roh:
|
||||||
try:
|
try:
|
||||||
self.geparkt_seit_ts = datetime.datetime.fromisoformat(roh)
|
geladen = datetime.datetime.fromisoformat(roh)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.geparkt_seit_ts = None
|
geladen = None
|
||||||
|
# Auch beim LADEN pruefen, nicht nur beim Setzen: ein einmal
|
||||||
|
# falsch gespeicherter Wert (auf der Instanz des Eigentuemers am
|
||||||
|
# 02.09.2026 die Unix-Epoche, weil der Meldezeit-Sensor 0 lieferte)
|
||||||
|
# ueberlebt sonst jeden Neustart - und _parkbeginn_nachholen()
|
||||||
|
# springt nicht ein, weil ja etwas gespeichert ist.
|
||||||
|
if geladen is not None and not verlauf.geraetezeit_plausibel(
|
||||||
|
geladen, datetime.datetime.now(datetime.UTC)
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Gespeicherter Parkbeginn %s ist unglaubwuerdig - verworfen",
|
||||||
|
geladen.isoformat(),
|
||||||
|
)
|
||||||
|
geladen = None
|
||||||
|
self.geparkt_seit_ts = geladen
|
||||||
|
|
||||||
async def _laufzeit_sichern(self) -> None:
|
async def _laufzeit_sichern(self) -> None:
|
||||||
await self._store.async_save({
|
await self._store.async_save({
|
||||||
@@ -422,7 +436,7 @@ class Koordinator:
|
|||||||
gemeldet = datetime.datetime.fromtimestamp(epoche, datetime.UTC)
|
gemeldet = datetime.datetime.fromtimestamp(epoche, datetime.UTC)
|
||||||
except (ValueError, OSError, OverflowError):
|
except (ValueError, OSError, OverflowError):
|
||||||
gemeldet = None
|
gemeldet = None
|
||||||
if gemeldet is not None and gemeldet <= jetzt:
|
if verlauf.geraetezeit_plausibel(gemeldet, jetzt):
|
||||||
seit = gemeldet
|
seit = gemeldet
|
||||||
|
|
||||||
await self.geparkt_seit_setzen(seit)
|
await self.geparkt_seit_setzen(seit)
|
||||||
@@ -431,6 +445,11 @@ class Koordinator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def geparkt_seit_setzen(self, ts: datetime.datetime | None) -> None:
|
async def geparkt_seit_setzen(self, ts: datetime.datetime | None) -> None:
|
||||||
|
if ts is not None and not verlauf.geraetezeit_plausibel(
|
||||||
|
ts, datetime.datetime.now(datetime.UTC)
|
||||||
|
):
|
||||||
|
_LOGGER.warning("Parkbeginn %s ist unglaubwuerdig - nicht gesetzt", ts.isoformat())
|
||||||
|
ts = 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.2.5",
|
"version": "2026.9.2.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"],
|
||||||
|
|||||||
@@ -197,6 +197,39 @@ MELDEZEIT_TAKT_S = 0.05
|
|||||||
# falsch gestellte Geräteuhr darf die Auswertung nicht mitreißen.
|
# falsch gestellte Geräteuhr darf die Auswertung nicht mitreißen.
|
||||||
MELDEZEIT_VORLAUF = datetime.timedelta(minutes=5)
|
MELDEZEIT_VORLAUF = datetime.timedelta(minutes=5)
|
||||||
|
|
||||||
|
# Und wie weit sie hoechstens ZURUECKliegen darf.
|
||||||
|
#
|
||||||
|
# Der Grund ist real: am 02.09.2026 stand auf der Instanz des Eigentuemers
|
||||||
|
# "Geparkt seit 20698 Tg. 9 Std. 39 Min." - das ist der 31.12.1969, also die
|
||||||
|
# Unix-Epoche. Der Meldezeit-Sensor hatte 0 gemeldet (dasselbe Verhalten wie
|
||||||
|
# external_power_voltage bei abgestecktem Dongle, siehe Abschnitt Z), und
|
||||||
|
# fromtimestamp(0) ergibt genau diesen Zeitpunkt. Geprueft wurde bis dahin nur
|
||||||
|
# die Zukunft, nicht die Vergangenheit.
|
||||||
|
#
|
||||||
|
# 365 Tage (Vorgabe des Eigentuemers). Das Geraet puffert real nur Stunden bis
|
||||||
|
# Tage (On-Stop-Sendeperiode 24 h), die Grenze ist also sehr weit gefasst - und
|
||||||
|
# genau deshalb ungefaehrlich: sie kann keine echte gepufferte Meldung
|
||||||
|
# verwerfen. Was sie fangen soll, liegt Jahrzehnte daneben: eine 0 ergibt 1970,
|
||||||
|
# und eine faelschlich zugeordnete ID-Entitaet (im flespi-Geraet heissen drei
|
||||||
|
# davon aehnlich wie der Zeitstempel) ebenfalls - 1304 etwa waere der
|
||||||
|
# 01.01.1970, 00:21 Uhr.
|
||||||
|
MELDEZEIT_RUECKLAUF = datetime.timedelta(days=365)
|
||||||
|
|
||||||
|
|
||||||
|
def geraetezeit_plausibel(
|
||||||
|
gemeldet: datetime.datetime | None, jetzt: datetime.datetime
|
||||||
|
) -> bool:
|
||||||
|
"""Kann dieser Zeitstempel wirklich vom Geraet stammen?
|
||||||
|
|
||||||
|
Weder aus der Zukunft (falsch gestellte Uhr) noch aus grauer Vorzeit -
|
||||||
|
siehe MELDEZEIT_RUECKLAUF fuer den Fall, der das ausgeloest hat."""
|
||||||
|
if gemeldet is None:
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
gemeldet <= jetzt + MELDEZEIT_VORLAUF
|
||||||
|
and gemeldet >= jetzt - MELDEZEIT_RUECKLAUF
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def geraetezeit(
|
async def geraetezeit(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -247,6 +280,14 @@ async def geraetezeit(
|
|||||||
gemeldet.isoformat(), standard.isoformat(),
|
gemeldet.isoformat(), standard.isoformat(),
|
||||||
)
|
)
|
||||||
return standard
|
return standard
|
||||||
|
if gemeldet < standard - MELDEZEIT_RUECKLAUF:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Meldezeit %s liegt mehr als %s Tage zurueck (jetzt %s) - verworfen. "
|
||||||
|
"Der Sensor meldet vermutlich 0, oder im Setup ist statt des "
|
||||||
|
"Zeitstempels eine ID-Entitaet zugeordnet",
|
||||||
|
gemeldet.isoformat(), MELDEZEIT_RUECKLAUF.days, standard.isoformat(),
|
||||||
|
)
|
||||||
|
return standard
|
||||||
return gemeldet
|
return gemeldet
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user