Fahrterkennung: FMM003-Funklöcher reißen keine Fahrt mehr auseinander
Die Zündungs-Entität stammt vom FMM003-Tracker; verliert er unterwegs kurz die Verbindung, meldet Home Assistant "unavailable" statt eines echten Zündungszustands. Die Live-Erkennung wertete das bisher wie "aus" - eine Funklücke über der Pausenzeit teilte eine durchgehende Fahrt in zwei. zuendung_geaendert() ignoriert "unavailable"/"unknown" jetzt vollständig und leitet "fährt gerade" aus dem eigenen Zwischenstand (fahrt_start_ts) statt aus dem letzten Rohwert ab. historienimport.py bekommt zusätzlich eine Plausibilitätsprüfung: eine errechnete Durchschnittsgeschwindigkeit über 300 km/h deutet auf einen Kilometerstand-Ausreißer an der Fahrtgrenze hin, nicht auf eine echte Fahrt - die Strecke wird dann verworfen statt eine unmögliche Fahrt anzuzeigen. Tankerkennung: optionaler zweiter Sensor für das Tankvolumen in Litern (TANK_LITER_SENSOR, direkt vom CAN) neben dem bisherigen Prozent-Füllstand. Ist er zugeordnet, übernimmt er die automatische Tankerkennung vollständig - genauer als der Umweg über das im Fahrzeugprofil hinterlegte Tankvolumen, und der erkannte Anstieg liefert gleich eine grobe Vorbelegung für die getankte Menge statt eines leeren Feldes. Ohne den Sensor bleibt alles beim Alten. Der historische Import zieht mit derselben Präferenz nach. Manifest auf 2026.8.25.2, beide Änderungsrunden live im Testcontainer verifiziert (py_compile, Neustart, sauberes Setup-Log). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,7 @@ import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .fahrterkennung import leere_fahrt, pausenzeit_sekunden
|
||||
from .tankerkennung import leerer_tankvorgang, schwelle_prozent
|
||||
from .tankerkennung import LITER_SCHWELLE, leerer_tankvorgang, schwelle_prozent
|
||||
from .verlauf import Verlaufspunkt, verlauf_lesen, wert_bei, zahl
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -60,6 +60,15 @@ TANK_DUBLETTE_MIN = 90
|
||||
# fluten sie den Bestand aber mit Nulleinträgen. Bewusst konservativ.
|
||||
MINDESTDAUER_S = 60
|
||||
|
||||
# Kein Auto dieser Art erreicht diesen Schnitt. Eine errechnete
|
||||
# Durchschnittsgeschwindigkeit darüber ist kein Beleg für eine schnelle
|
||||
# Fahrt, sondern für einen fehlerhaften Kilometerstand-Sprung am Fahrtrand
|
||||
# (z. B. ein Ausreißer im Verlauf oder ein knapp daneben liegender
|
||||
# wert_bei()-Treffer über eine Lücke hinweg). Die Strecke wird dann verworfen
|
||||
# statt eine physikalisch unmögliche Fahrt anzuzeigen - die Fahrt bleibt
|
||||
# "offen", genau wie bei fehlendem Kilometerstand.
|
||||
UNPLAUSIBLE_KMH = 300
|
||||
|
||||
|
||||
def als_zeit(wert: object) -> datetime.datetime | None:
|
||||
"""Akzeptiert, was die Oberfläche schickt: ISO mit oder ohne Zeitzone.
|
||||
@@ -162,6 +171,16 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
durchschnitt = None
|
||||
if distanz is not None and dauer_s > 0:
|
||||
durchschnitt = round(distanz / (dauer_s / 3600.0), 1)
|
||||
if durchschnitt > UNPLAUSIBLE_KMH:
|
||||
_LOGGER.warning(
|
||||
"Kilometerstand für Fahrt %s bis %s verworfen: unmögliche %s km/h "
|
||||
"im Schnitt (%s km in %s s) - vermutlich ein Ausreißer im Verlauf. "
|
||||
"Die Fahrt wird trotzdem angelegt, bleibt aber ohne Strecke.",
|
||||
f_start.isoformat(), f_ende.isoformat(), durchschnitt, distanz, dauer_s,
|
||||
)
|
||||
distanz = None
|
||||
odo_start = odo_end = None
|
||||
durchschnitt = None
|
||||
|
||||
fahrt = leere_fahrt(f_start, f_ende, "import")
|
||||
fahrt.update({
|
||||
@@ -185,15 +204,19 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
|
||||
|
||||
async def _tankvorgaenge_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
"""Tankvorgänge aus dem Füllstandsverlauf - dieselbe Tiefststand-Logik wie
|
||||
in der Live-Erkennung: jeder Anstieg über die Schwelle gegen den zuletzt
|
||||
gesehenen Tiefststand ist ein Tankvorgang, nicht jeder Anstieg gegen den
|
||||
unmittelbar vorherigen Wert."""
|
||||
verlauf = verlaeufe["tank"]
|
||||
"""Tankvorgänge aus dem Füllstands- oder Tankvolumenverlauf - dieselbe
|
||||
Tiefststand-Logik wie in der Live-Erkennung (tankerkennung.py): jeder
|
||||
Anstieg über die Schwelle gegen den zuletzt gesehenen Tiefststand ist ein
|
||||
Tankvorgang, nicht jeder Anstieg gegen den unmittelbar vorherigen Wert.
|
||||
Dieselbe Präferenz wie live auch: ist ein Litersensor zugeordnet,
|
||||
übernimmt der - genauer, und der Anstieg dient gleich als grobe
|
||||
Vorbelegung für die getankte Menge."""
|
||||
mit_liter = bool(k.zuordnung.werte.TANK_LITER_SENSOR)
|
||||
verlauf = verlaeufe["tank_liter"] if mit_liter else verlaeufe["tank"]
|
||||
if not verlauf:
|
||||
return {"angelegt": 0, "uebersprungen": 0}
|
||||
|
||||
schwelle = schwelle_prozent(await k.ablage.profil_lesen())
|
||||
schwelle = LITER_SCHWELLE if mit_liter else schwelle_prozent(await k.ablage.profil_lesen())
|
||||
km_verlauf = verlaeufe["km"]
|
||||
bestehende = await k.ablage.tankvorgaenge_lesen()
|
||||
fenster_s = TANK_DUBLETTE_MIN * 60
|
||||
@@ -218,7 +241,8 @@ async def _tankvorgaenge_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
if tiefststand is None or aktuell <= tiefststand:
|
||||
tiefststand = aktuell
|
||||
continue
|
||||
if aktuell - tiefststand < schwelle:
|
||||
anstieg = aktuell - tiefststand
|
||||
if anstieg < schwelle:
|
||||
continue
|
||||
|
||||
if any(abs((bekannt - ts).total_seconds()) < fenster_s for bekannt in bekannte_zeiten):
|
||||
@@ -229,6 +253,8 @@ async def _tankvorgaenge_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
odometer_km = wert_bei(km_verlauf, ts)
|
||||
tankvorgang = leerer_tankvorgang(ts.isoformat(), "import")
|
||||
tankvorgang["odometer_km"] = odometer_km
|
||||
if mit_liter:
|
||||
tankvorgang["liters"] = round(anstieg, 1)
|
||||
tankvorgang["distance_km"] = _distanz_zum_vorherigen(
|
||||
ts.isoformat(), odometer_km, neue + bestehende
|
||||
)
|
||||
@@ -339,6 +365,7 @@ async def importieren(k: Koordinator, start: object, ende: object) -> None:
|
||||
"zuendung": await verlauf_lesen(k.hass, werte.ZUENDUNG_SENSOR, von, bis),
|
||||
"km": await verlauf_lesen(k.hass, werte.KM_SENSOR, von, bis),
|
||||
"tank": await verlauf_lesen(k.hass, werte.TANK_SENSOR, von, bis),
|
||||
"tank_liter": await verlauf_lesen(k.hass, werte.TANK_LITER_SENSOR, von, bis),
|
||||
"batterie": await verlauf_lesen(k.hass, werte.BATTERIE_SENSOR, von, bis),
|
||||
"lat": await verlauf_lesen(k.hass, werte.STANDORT_LAT_SENSOR, von, bis),
|
||||
"lon": await verlauf_lesen(k.hass, werte.STANDORT_LON_SENSOR, von, bis),
|
||||
|
||||
Reference in New Issue
Block a user