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 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,8 @@ from __future__ import annotations
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.recorder import get_instance, history
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -78,6 +80,19 @@ async def verlauf_lesen(
|
||||
return []
|
||||
|
||||
|
||||
def fahrtsignal(werte: object) -> str:
|
||||
"""Die Entität, die Fahrtbeginn und -ende auslöst.
|
||||
|
||||
Der Trip-Status des Geräts, wenn er zugeordnet ist - sonst die Zündung,
|
||||
wie vor der Trennung der beiden Rollen. Damit laufen bestehende
|
||||
Installationen unverändert weiter, ohne dass jemand etwas zuordnen muss.
|
||||
|
||||
Eine gemeinsame Stelle, weil sonst Livepfad (fahrterkennung.py) und
|
||||
Rückblick (historienimport.py) auseinanderlaufen könnten - dieselbe Regel
|
||||
wie bei UNPLAUSIBLE_KMH und MINDESTDAUER_S."""
|
||||
return getattr(werte, "TRIP_SENSOR", "") or getattr(werte, "ZUENDUNG_SENSOR", "")
|
||||
|
||||
|
||||
def zeit_normalisiert(wert: object) -> datetime.datetime | None:
|
||||
"""Akzeptiert ISO-Zeit mit oder ohne Zeitzone und normalisiert auf UTC.
|
||||
|
||||
@@ -139,6 +154,75 @@ def durchschnitt_kmh(distanz_km: float | None, dauer_s: float | None) -> float |
|
||||
return round(distanz_km / (dauer_s / 3600.0), 1)
|
||||
|
||||
|
||||
# Wie lange auf die Meldezeit desselben Datensatzes gewartet wird.
|
||||
#
|
||||
# Die flespi-Integration setzt die Entitäten eines Datensatzes nacheinander:
|
||||
# am 01.09.2026 gemessen stand die Zündung um 07:35:23.633 und die zugehörige
|
||||
# Meldezeit 2 ms später um 07:35:23.635. Ohne diese kurze Wartezeit läse man
|
||||
# den Zeitstempel des VORIGEN Datensatzes - beim Fahren zehn Sekunden daneben,
|
||||
# im Stand aber möglicherweise Stunden, weil das Gerät dort nur alle paar
|
||||
# Stunden einen Satz schreibt (Data Acquisition, On Stop: 43200 s).
|
||||
MELDEZEIT_FRIST_S = 2.0
|
||||
MELDEZEIT_TAKT_S = 0.05
|
||||
|
||||
# Wie weit die Geräteuhr der von Home Assistant vorauslaufen darf, bevor der
|
||||
# Zeitstempel verworfen wird. Etwas Luft für Drift, aber keine Stunden - eine
|
||||
# falsch gestellte Geräteuhr darf die Auswertung nicht mitreißen.
|
||||
MELDEZEIT_VORLAUF = datetime.timedelta(minutes=5)
|
||||
|
||||
|
||||
async def geraetezeit(
|
||||
hass: HomeAssistant,
|
||||
sensor: str | None,
|
||||
ereigniszeit: datetime.datetime | None,
|
||||
standard: datetime.datetime,
|
||||
) -> datetime.datetime:
|
||||
"""Der Zeitstempel, den das Gerät seinem Datensatz mitgegeben hat - oder
|
||||
`standard`, wenn er fehlt oder unglaubwürdig ist.
|
||||
|
||||
Warum das nötig ist: `standard` ist die Uhrzeit, zu der Home Assistant den
|
||||
Zustandswechsel verarbeitet hat. Hat das Gerät gepuffert - Funkloch,
|
||||
Tiefgarage, Tiefschlaf -, liegt die Wahrheit beliebig weit davor. Am
|
||||
01.09.2026 gemessen: ein Datensatz mit Fahrtende trug die Gerätezeit
|
||||
07:29:04 und kam um 07:35:23 an, sechs Minuten später.
|
||||
|
||||
Der Wert ist eine Unix-Sekunde in UTC, nicht Ortszeit - ebenfalls
|
||||
nachgemessen: der Versatz zur UTC-Uhr von Home Assistant lag bei Sekunden,
|
||||
nicht bei zwei Stunden. Eine Zeitzonenumrechnung wäre nicht nur unnötig,
|
||||
sie baute einen Zwei-Stunden-Fehler ein.
|
||||
|
||||
Gemeinsam für Fahrterkennung und Tankerkennung: beide legen Datensätze mit
|
||||
einem Zeitstempel an und dürfen ihn nie unterschiedlich bestimmen - dieselbe
|
||||
Regel wie bei UNPLAUSIBLE_KMH und MINDESTDAUER_S."""
|
||||
if not sensor:
|
||||
return standard
|
||||
|
||||
if ereigniszeit is not None:
|
||||
wartezeit = 0.0
|
||||
while wartezeit < MELDEZEIT_FRIST_S:
|
||||
zustand = hass.states.get(sensor)
|
||||
if zustand is not None and zustand.last_updated >= ereigniszeit:
|
||||
break
|
||||
await asyncio.sleep(MELDEZEIT_TAKT_S)
|
||||
wartezeit += MELDEZEIT_TAKT_S
|
||||
|
||||
zustand = hass.states.get(sensor)
|
||||
try:
|
||||
gemeldet = datetime.datetime.fromtimestamp(
|
||||
float(zustand.state), datetime.UTC # type: ignore[union-attr]
|
||||
)
|
||||
except (AttributeError, TypeError, ValueError, OSError, OverflowError):
|
||||
return standard
|
||||
|
||||
if gemeldet > standard + MELDEZEIT_VORLAUF:
|
||||
_LOGGER.warning(
|
||||
"Meldezeit %s liegt in der Zukunft (jetzt %s) - verworfen",
|
||||
gemeldet.isoformat(), standard.isoformat(),
|
||||
)
|
||||
return standard
|
||||
return gemeldet
|
||||
|
||||
|
||||
def wert_bei(
|
||||
verlauf: list[Verlaufspunkt], zeitpunkt: datetime.datetime
|
||||
) -> float | None:
|
||||
|
||||
Reference in New Issue
Block a user