Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt (2026.9.4.1)
Das Fahrtfenster stand seit dem 01.09. in der Gerätezeit, der Verlauf war nach Ankunftszeit sortiert. Zwei Uhren, und das Gerät puffert: was verspätet ankam, fiel aus einem Fenster heraus, in dem es der Sache nach lag. Am 03.09. hat das eine Fahrt gekostet - 26 von 48 GPS-Punkten und 3,0 statt 7 km. verlauf_lesen() stempelt jeden Punkt jetzt mit der Gerätezeit seines Datensatzes. Eine Stelle, an der alle Verbraucher vorbeikommen. Dazu zwei Riegel aus derselben Untersuchung: ein Rückschritt von 43 Metern ist keine Zählerrücksetzung mehr (aus 87 Metern wurden vorher 13,7 km), und das Fahrtende wird auf das letzte Lebenszeichen des Geräts geklemmt - der Nachlauf ist eine Rechnung, kein Messwert. Am echten Recorder nachgerechnet: Tacho 3,0 -> 7,0 km, GNSS 2,952 -> 6,877 km, Route 26 -> 47 Punkte, Ende 23:21:57 -> 23:17:48. Beim ersten Lauf haben zwei Fahrten eine Strecke bekommen, die vorher gar keine hatten. Nicht das Gerät war schuld: es hat lückenlos aufgezeichnet. Die zehn Minuten Funkstille waren eine offene, aber tote TCP-Sitzung (flespi-Log: 703 s, 16 Nachrichten) - kein Funkloch. Begründung und die Empfehlung fürs Gerät stehen in AGENTS.md, Abschnitt CB. Dazu die neun Paritätsbefunde, alle Richtung Panel gelöst - darunter ein unmaskierter Punkt in beiden Codebasen, der aus "vor 3 Tage" ein "vor 3 Tag" machte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ from .verlauf import (
|
||||
MINDESTDAUER_S,
|
||||
fahrtsignal,
|
||||
geraetezeit,
|
||||
geraetezeit_plausibel,
|
||||
verlauf_lesen,
|
||||
zeit_normalisiert,
|
||||
)
|
||||
@@ -180,6 +181,34 @@ def leere_fahrt(
|
||||
|
||||
|
||||
|
||||
async def _letzter_datensatz(
|
||||
k: Koordinator, start_ts: datetime.datetime, bis: datetime.datetime
|
||||
) -> datetime.datetime | None:
|
||||
"""Der letzte Datensatz, den das Geraet im Fenster [start_ts, bis]
|
||||
geschrieben hat - in seiner eigenen Zeit.
|
||||
|
||||
Der Meldezeit-Sensor traegt als WERT die Geraetezeit; hier ist also nichts
|
||||
umzustempeln, der Wert ist die Antwort. Gelesen wird bis jetzt, weil ein
|
||||
Datensatz aus dem Fenster erst Minuten spaeter ankommen kann (siehe
|
||||
verlauf.NACHREICHFENSTER)."""
|
||||
sensor = k.zuordnung.werte.MELDEZEIT_SENSOR
|
||||
if not sensor:
|
||||
return None
|
||||
jetzt = datetime.datetime.now(datetime.UTC)
|
||||
reihe = await verlauf_lesen(k.hass, sensor, start_ts, max(jetzt, bis))
|
||||
letzter: datetime.datetime | None = None
|
||||
for _ts, wert in reihe:
|
||||
try:
|
||||
gemeldet = datetime.datetime.fromtimestamp(float(wert), datetime.UTC)
|
||||
except (TypeError, ValueError, OSError, OverflowError):
|
||||
continue
|
||||
if not geraetezeit_plausibel(gemeldet, jetzt):
|
||||
continue
|
||||
if start_ts < gemeldet <= bis and (letzter is None or gemeldet > letzter):
|
||||
letzter = gemeldet
|
||||
return letzter
|
||||
|
||||
|
||||
async def _echtes_ende(
|
||||
k: Koordinator,
|
||||
start_ts: datetime.datetime,
|
||||
@@ -220,6 +249,27 @@ async def _echtes_ende(
|
||||
return start_ts
|
||||
if ueber_trip:
|
||||
await _nachlauf_gegenpruefen(k, start_ts, signal_ende, gerechnet)
|
||||
|
||||
# Nie hinter dem letzten Lebenszeichen des Geraets.
|
||||
#
|
||||
# Der Nachlauf ist eine Rechnung, kein Messwert: er unterstellt, dass die
|
||||
# gemeldete Zuendung wirklich eine verzoegerte Zuendung war. Am 03.09.2026
|
||||
# war sie das nicht - der Dongle war um 23:17:48 stromlos (Stecker
|
||||
# gezogen), meldete deshalb ZUENDUNG_NACHLAUF_S spaeter mangels Drehzahl
|
||||
# -aus- und konnte den Datensatz erst um 23:24:27 absetzen. Abzueglich der
|
||||
# 180 s ergab das ein Fahrtende um 23:21:57 - vier Minuten nach dem
|
||||
# letzten Datensatz, den es ueberhaupt gibt. Das Auto stand da laengst.
|
||||
#
|
||||
# Was wir wissen koennen, endet beim letzten Datensatz. Alles danach waere
|
||||
# erfunden, und eine erfundene Zahl ist schlechter als eine knappe.
|
||||
letzter = await _letzter_datensatz(k, start_ts, gerechnet)
|
||||
if letzter is not None and letzter < gerechnet:
|
||||
_LOGGER.info(
|
||||
"Fahrtende auf %s geklemmt - danach hat das Geraet nichts mehr "
|
||||
"gemeldet (gerechnet waere %s gewesen)",
|
||||
letzter.isoformat(), gerechnet.isoformat(),
|
||||
)
|
||||
return letzter
|
||||
return gerechnet
|
||||
|
||||
|
||||
@@ -244,7 +294,9 @@ async def _nachlauf_gegenpruefen(
|
||||
sensor = k.zuordnung.werte.ZUENDUNG_SENSOR
|
||||
if not sensor or sensor == fahrtsignal(k.zuordnung.werte):
|
||||
return
|
||||
punkte = await verlauf_lesen(k.hass, sensor, start_ts, signal_ende)
|
||||
punkte = await verlauf_lesen(
|
||||
k.hass, sensor, start_ts, signal_ende, k.zuordnung.werte.MELDEZEIT_SENSOR
|
||||
)
|
||||
aus = [ts for ts, wert in punkte if wert == "off"]
|
||||
if not aus:
|
||||
return
|
||||
@@ -724,7 +776,9 @@ async def _letztes_lebenszeichen(
|
||||
Kilometerstände ohnehin nach.
|
||||
"""
|
||||
jetzt = datetime.datetime.now(datetime.UTC)
|
||||
punkte = await verlauf_lesen(k.hass, sensor, seit, jetzt)
|
||||
punkte = await verlauf_lesen(
|
||||
k.hass, sensor, seit, jetzt, k.zuordnung.werte.MELDEZEIT_SENSOR
|
||||
)
|
||||
if punkte:
|
||||
return punkte[-1][0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user