Fahrten: Verbrauch-Näherung, 0V-Zustandsanzeige gefiltert, Plausibilitätsgrenze für die Live-Vervollständigung
Verbrauch (l/100km) war bislang totes Schema - kein Codepfad füllte verbrauch_l_100km je. Jetzt aus der Literstand-Differenz (TANK_LITER_SENSOR) über die Distanz genähert, live und beim Import, in beiden Frontends. Die "Zustand"-Kachel zeigte die Batteriespannung ungefiltert direkt vom Sensor, unabhängig von der Plausibilitätsgrenze der Verlaufsaufzeichnung - ein Sensorausreißer (0V) zeigte sich dort weiterhin, obwohl die Messwertliste ihn längst verwarf. Dieselbe Grenze gilt jetzt auch für diesen Anzeigepfad. Reale Fahrtendaten zeigten eine Fahrt mit 22km in 67s (~1180 km/h) - die Live-Vervollständigung (screening.py) hatte anders als der Import keine Plausibilitätsprüfung der Durchschnittsgeschwindigkeit. Jetzt gemeinsam in verlauf.py (UNPLAUSIBLE_KMH/durchschnitt_kmh) für beide Pfade. Dabei einen zweiten echten Bug gefunden: _fahrt_screenen() zog sein Ergebnis nie ins In-Memory-Objekt nach (nur in die Ablage) - eine im selben Durchlauf gerade erst ermittelte Distanz blieb für spätere Schritte (z. B. Verbrauch) bis zum nächsten Screening unsichtbar. Beide Stellen jetzt behoben. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -22,7 +22,14 @@ import datetime
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .verlauf import naechster_wert, route_aus_verlauf, verlauf_lesen
|
||||
from .verlauf import (
|
||||
UNPLAUSIBLE_KMH,
|
||||
durchschnitt_kmh,
|
||||
naechster_wert,
|
||||
route_aus_verlauf,
|
||||
verbrauch_aus_literstaenden,
|
||||
verlauf_lesen,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .koordinator import Koordinator
|
||||
@@ -47,9 +54,30 @@ def _als_zeit(wert: str | None) -> datetime.datetime | None:
|
||||
def _vollstaendig(aenderungen: dict, fahrt: dict) -> dict:
|
||||
"""Ergänzt Strecke, Quelle und Status, sobald beide Kilometerstände da
|
||||
sind. Eine Fahrt ist genau dann "vollständig", wenn ihre Strecke feststeht
|
||||
- "offen" heißt nicht "unterwegs", sondern "Daten fehlen noch"."""
|
||||
- "offen" heißt nicht "unterwegs", sondern "Daten fehlen noch".
|
||||
|
||||
Verwirft eine physikalisch unplausible Durchschnittsgeschwindigkeit
|
||||
(UNPLAUSIBLE_KMH) statt sie anzuzeigen, statt der bislang unbehandelten
|
||||
Lücke gegenüber historienimport.py: eine falsch abgegrenzte Fahrt (z. B.
|
||||
durch ein chatterndes Zündungssignal) kann sonst einen Kilometerstand-
|
||||
Sprung wie "22 km in 67 s" als echte Strecke zeigen. Sowohl odo_start als
|
||||
auch odo_end werden dann verworfen (nicht nur die Strecke) - die Fahrt
|
||||
bleibt "offen" und wird beim nächsten Screening erneut versucht, statt
|
||||
einen unmöglichen Wert dauerhaft zu speichern."""
|
||||
if fahrt.get("odo_start") is not None and fahrt.get("odo_end") is not None:
|
||||
aenderungen["distance_km"] = round(fahrt["odo_end"] - fahrt["odo_start"], 1)
|
||||
distanz = round(fahrt["odo_end"] - fahrt["odo_start"], 1)
|
||||
geschwindigkeit = durchschnitt_kmh(distanz, fahrt.get("duration_s"))
|
||||
if geschwindigkeit is not None and geschwindigkeit > UNPLAUSIBLE_KMH:
|
||||
_LOGGER.warning(
|
||||
"Fahrt %s: Kilometerstand verworfen - unmögliche %s km/h im "
|
||||
"Schnitt (%s km in %s s), vermutlich ein Ausreißer im "
|
||||
"Verlauf oder eine falsch abgegrenzte Fahrt.",
|
||||
fahrt.get("trip_id"), geschwindigkeit, distanz, fahrt.get("duration_s"),
|
||||
)
|
||||
aenderungen["odo_start"] = None
|
||||
aenderungen["odo_end"] = None
|
||||
return aenderungen
|
||||
aenderungen["distance_km"] = distanz
|
||||
aenderungen["km_quelle"] = "odometer"
|
||||
aenderungen["status"] = "vollständig"
|
||||
return aenderungen
|
||||
@@ -81,6 +109,15 @@ async def durchfuehren(k: Koordinator) -> None:
|
||||
for fahrt in ohne_position:
|
||||
await _position_screenen(k, lat_sensor, lon_sensor, fahrt)
|
||||
|
||||
tank_liter_sensor = k.zuordnung.werte.TANK_LITER_SENSOR
|
||||
if tank_liter_sensor:
|
||||
ohne_verbrauch = [
|
||||
f for f in fahrten
|
||||
if f.get("verbrauch_l_100km") is None and f.get("distance_km")
|
||||
]
|
||||
for fahrt in ohne_verbrauch:
|
||||
await _verbrauch_screenen(k, tank_liter_sensor, fahrt)
|
||||
|
||||
await _lueckenlos_verketten(k)
|
||||
await k.fahrten_veroeffentlichen()
|
||||
|
||||
@@ -115,7 +152,15 @@ async def _fahrt_screenen(k: Koordinator, km_sensor: str, fahrt: dict) -> None:
|
||||
|
||||
if geaendert:
|
||||
aenderungen = {"odo_start": fahrt.get("odo_start"), "odo_end": fahrt.get("odo_end")}
|
||||
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], _vollstaendig(aenderungen, fahrt))
|
||||
aenderungen = _vollstaendig(aenderungen, fahrt)
|
||||
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], aenderungen)
|
||||
# Das In-Memory-Objekt sofort mitziehen (nicht nur die Ablage) - sonst
|
||||
# sehen spätere Schritte innerhalb desselben durchfuehren()-Laufs
|
||||
# (z. B. _verbrauch_screenen(), das eine bekannte distance_km
|
||||
# braucht) noch den alten Stand, obwohl die Strecke gerade erst in
|
||||
# diesem Durchlauf ermittelt wurde. _position_screenen() macht das
|
||||
# bereits genauso.
|
||||
fahrt.update(aenderungen)
|
||||
|
||||
|
||||
async def _position_screenen(
|
||||
@@ -161,6 +206,29 @@ async def _position_screenen(
|
||||
fahrt.update(aenderungen)
|
||||
|
||||
|
||||
async def _verbrauch_screenen(
|
||||
k: Koordinator, tank_liter_sensor: str, fahrt: dict
|
||||
) -> None:
|
||||
"""Trägt den Verbrauch (l/100 km) nach, sobald die Distanz feststeht und
|
||||
der Tankfüllstand (Liter) das hergibt - siehe verbrauch_aus_literstaenden()
|
||||
für die Näherung selbst und ihre Begrenzung."""
|
||||
start = _als_zeit(fahrt.get("ts_start"))
|
||||
ende = _als_zeit(fahrt.get("ts_end"))
|
||||
if start is None or ende is None:
|
||||
return
|
||||
|
||||
verlauf = await verlauf_lesen(k.hass, tank_liter_sensor, start - SUCHFENSTER, ende + SUCHFENSTER)
|
||||
if not verlauf:
|
||||
return
|
||||
|
||||
verbrauch = verbrauch_aus_literstaenden(
|
||||
naechster_wert(start, verlauf), naechster_wert(ende, verlauf), fahrt.get("distance_km")
|
||||
)
|
||||
if verbrauch is not None:
|
||||
await k.ablage.fahrt_aktualisieren(fahrt["trip_id"], {"verbrauch_l_100km": verbrauch})
|
||||
fahrt["verbrauch_l_100km"] = verbrauch
|
||||
|
||||
|
||||
async def _lueckenlos_verketten(k: Koordinator) -> None:
|
||||
"""Fahrt-Ende(N) == Fahrt-Start(N+1), solange dazwischen nicht gefahren
|
||||
wurde.
|
||||
|
||||
Reference in New Issue
Block a user