Fahrtzeiten vom Geraet, Trip und Zuendung getrennt (2026.9.1.3)

Enthaelt 2026.9.1.2, das nie einzeln ausgeliefert wurde.

FAHRT- UND TANKZEITEN KOMMEN VOM GERAET

Fahrtbeginn war datetime.now() - die Uhrzeit, zu der Home Assistant den
Wechsel verarbeitet. Hat das Geraet gepuffert (Funkloch, Tiefgarage,
Tiefschlaf), liegt die Wahrheit beliebig weit davor. Gemessen: ein Datensatz
mit Fahrtende trug die Geraetezeit 07:29:04 und kam um 07:35:23 an.

Neue Sensorrolle MELDEZEIT_SENSOR (message_timestamp, Unix-Sekunden). Der
Wert ist UTC, keine Ortszeit - nachgemessen lag der Versatz zur UTC-Uhr von
HA bei Sekunden, nicht bei zwei Stunden; eine Zeitzonenumrechnung baute einen
Zwei-Stunden-Fehler ein.

geraetezeit() liegt in verlauf.py, weil Fahrt- und Tankerkennung denselben
Zeitstempel bestimmen muessen. tankerkennung._automatisch_anlegen() stempelte
bisher ebenfalls mit now(); ein aus einem gepufferten Datensatz erkannter
Tankvorgang trug damit die Uhrzeit des Auftauchens.

Die flespi-Integration setzt die Entitaeten eines Datensatzes nacheinander -
gemessen Zuendung 07:35:23.633, zugehoerige Meldezeit 2 ms spaeter. Wer
sofort liest, bekommt den vorigen Datensatz: beim Fahren zehn Sekunden
daneben, im Stand Stunden (On Stop Min Period = 43200 s). geraetezeit()
wartet deshalb bis zu 2 s auf den passenden Wert. Zwei Notbremsen, beide live
ausgeloest: Zeitstempel in der Zukunft und Ende vor dem Beginn.

TRIP UND ZUENDUNG SIND ZWEI ROLLEN

ZUENDUNG_SENSOR zeigte auf das Trip-Signal; im Setup stand "Zuendung/ACC" ueber
einer Trip-Entitaet und die echte Zuendung war nirgends zugeordnet. Neu:
TRIP_SENSOR loest Fahrten aus, ZUENDUNG_SENSOR liefert nur noch die Anzeige
"faehrt/steht". verlauf.fahrtsignal() ist die eine Stelle, die entscheidet -
Trip-Status wenn zugeordnet, sonst Zuendung, damit bestehende Installationen
unveraendert weiterlaufen und Livepfad und Rueckblick nicht auseinanderlaufen.

VERIFIZIERT in audi_ha_test, jeder Weg einzeln:
- Fahrtbeginn 08:30:03 (Geraetezeit) statt 08:38:27 (Ankunft)
- Fahrtende 08:04:40 -> 08:09:22 statt 08:11:04 -> 08:11:24
- Tankvorgang 08:18:45 statt 08:38:51 - 20 Minuten frueher
- Meldezeit in der Zukunft und Ende vor Beginn: Warnung, Ankunft genommen
- Zuendung schalten legt keine Fahrt mehr an, Trip-Signal schon

Der Livetest fing dabei einen NameError, den py_compile nicht sehen konnte:
der Import von geraetezeit in tankerkennung.py fehlte. Kompiliert ist nicht
verifiziert. Alle Testdatensaetze wurden ueber die eigenen Dienste wieder
entfernt; Bestand unveraendert 11 Fahrten, 13 Tankvorgaenge, keine offene Fahrt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-01 10:40:21 +02:00
co-authored by Claude Opus 5
parent ff5a334e95
commit 4cc91c461b
8 changed files with 363 additions and 24 deletions
@@ -26,7 +26,13 @@ from typing import TYPE_CHECKING
from . import screening
from .ablage import neue_id
from .veroeffentlichung import zustand_oder_none
from .verlauf import MINDESTDAUER_S, verlauf_lesen, zeit_normalisiert
from .verlauf import (
MINDESTDAUER_S,
fahrtsignal,
geraetezeit,
verlauf_lesen,
zeit_normalisiert,
)
if TYPE_CHECKING:
from .koordinator import Koordinator
@@ -77,7 +83,23 @@ def leere_fahrt(start_ts: datetime.datetime, ende_ts: datetime.datetime, quelle:
async def zuendung_geaendert(k: Koordinator, neu: str | None, alt: str | None) -> None:
async def _geraetezeit(
k: Koordinator,
ereigniszeit: datetime.datetime | None,
standard: datetime.datetime,
) -> datetime.datetime:
"""Die Meldezeit des Geräts - siehe verlauf.geraetezeit()."""
return await geraetezeit(
k.hass, k.zuordnung.werte.MELDEZEIT_SENSOR, ereigniszeit, standard
)
async def zuendung_geaendert(
k: Koordinator,
neu: str | None,
alt: str | None,
ereigniszeit: datetime.datetime | None = None,
) -> None:
"""Reagiert auf jede Zustandsänderung der Zündungs-Entität.
"unavailable"/"unknown" ist kein Zündungszustand, sondern Funkstille des
@@ -119,9 +141,16 @@ async def zuendung_geaendert(k: Koordinator, neu: str | None, alt: str | None) -
# ganze Zeit lief.
fahrt_laeuft = k.fahrt_start_ts is not None
jetzt = datetime.datetime.now(datetime.UTC)
if an_jetzt and not fahrt_laeuft:
await k.fahrt_start_setzen(datetime.datetime.now(datetime.UTC))
_LOGGER.info("Fahrt gestartet um %s", k.fahrt_start_ts)
beginn = await _geraetezeit(k, ereigniszeit, jetzt)
await k.fahrt_start_setzen(beginn)
_LOGGER.info(
"Fahrt gestartet um %s (%s)",
k.fahrt_start_ts,
"Gerätezeit" if beginn != jetzt else "Ankunft in HA",
)
# Die Anzeige "fährt/steht" hängt an derselben Entität - sofort neu
# veröffentlichen, statt bis zum nächsten 20-Sekunden-Takt zu warten.
await k.fahrzeugstatus_veroeffentlichen()
@@ -134,7 +163,17 @@ async def zuendung_geaendert(k: Koordinator, neu: str | None, alt: str | None) -
# zu warten haette daraus eine halbe Stunde gemacht und zwei
# aufeinanderfolgende Fahrten verschmolzen, die das Geraet sauber
# getrennt hatte.
await fahrt_beenden(k, k.fahrt_start_ts, datetime.datetime.now(datetime.UTC))
ende = await _geraetezeit(k, ereigniszeit, jetzt)
# Ein Ende vor dem Beginn kann es nicht geben. Passiert, wenn die
# Meldezeit aus einem älteren Datensatz stammt als der Fahrtbeginn -
# dann ist die Ankunftszeit die ehrlichere Angabe.
if ende < k.fahrt_start_ts:
_LOGGER.warning(
"Meldezeit %s liegt vor dem Fahrtbeginn %s - nehme die Ankunft",
ende.isoformat(), k.fahrt_start_ts.isoformat(),
)
ende = jetzt
await fahrt_beenden(k, k.fahrt_start_ts, ende)
await k.fahrzeugstatus_veroeffentlichen()
@@ -157,7 +196,7 @@ async def nach_neustart_fortsetzen(k: Koordinator) -> None:
if k.fahrt_start_ts is None:
return
sensor = k.zuordnung.werte.ZUENDUNG_SENSOR
sensor = fahrtsignal(k.zuordnung.werte)
if zustand_oder_none(k.hass, sensor) == "on":
return # fährt noch - der Beobachter übernimmt wie sonst auch