Fahrten, Statistik, Trip-Timer (2026.8.31.3)
Der 15-Minuten-Timer war doppelt: der FMM003 wartet laut Konfiguration (Trip \ Odometer, Ignition OFF Timeout) selbst 900 s, die App noch einmal 15 Minuten. Zusammen eine halbe Stunde. Die Pausenregel ist damit entfallen - in der Live-Erkennung, im Historienimport und in beiden Oberflaechen. Der Schwellwert "ab wann ist es ein Parkplatz" hing an derselben Einstellung, hat damit aber nichts zu tun und steht jetzt als eigene Zahl. Hoechst- und Durchschnittsgeschwindigkeit je Fahrt. Der Durchschnitt folgt aus Strecke und Dauer, vmax aus der neuen Rolle GESCHWINDIGKEIT_SENSOR - laut Konfiguration liest das Geraet die Geschwindigkeit vom OBD/CAN. Es ist der groesste gemeldete Wert, nicht die tatsaechliche Spitze: alle zehn Sekunden ein Datensatz. So ist es auch beschriftet. Der Langzeitverbrauch war um einen ganzen Tankinhalt zu hoch. Gemessen wird die Strecke ZWISCHEN erstem und letztem Tankstop; verbraucht wurde darauf, was ab dem ZWEITEN hineinkam - der erste fuellte die Strecke davor. Bei 13 Tankvorgaengen rund 8 Prozent, im Testfall 16,7 statt 8,3 l/100 km. Der Test hatte die falsche Rechnung festgeschrieben und ist mitkorrigiert. Eine negative Strecke (falscher Kilometerstand an einem Tankvorgang) gilt jetzt als unplausibel statt als Null ohne Erklaerung. Verbrauch je Fahrt bekommt Grenzen: am Sensor nachgemessen loest can_fuel_volume 0,1 l auf, ein Schritt entspricht also 10/Strecke l/100 km Fehler. Unter 3 km sagt die Differenz nichts mehr, ueber 60 l/100 km ist es kein Verbrauch. Statistik: zusaetzlich "Absolut", und die Einheit steht jetzt in eckigen Klammern an der Rubrik statt vier Mal am Zeitraum. Fuenf Spalten passen auf einem Telefon nicht nebeneinander, das Raster bricht um. Radfoto in Reifen war ein reines <Bild> - Ersetzen und Loeschen von dort aus gar nicht erreichbar. Jetzt BildMitMenue wie im Panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,15 +42,16 @@ import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .batterie import SPANNUNG_MIN_V
|
||||
from .fahrterkennung import leere_fahrt, pausenzeit_sekunden
|
||||
from .fahrterkennung import leere_fahrt
|
||||
from .tankerkennung import LITER_SCHWELLE, leerer_tankvorgang, schwelle_prozent
|
||||
from .verlauf import (
|
||||
UNPLAUSIBLE_KMH,
|
||||
Verlaufspunkt,
|
||||
durchschnitt_kmh,
|
||||
hoechstwert_im_fenster,
|
||||
route_aus_verlauf,
|
||||
UNPLAUSIBLE_KMH,
|
||||
verbrauch_aus_literstaenden,
|
||||
verlauf_lesen,
|
||||
Verlaufspunkt,
|
||||
wert_bei,
|
||||
wert_in_naehe,
|
||||
zahl,
|
||||
@@ -83,15 +84,17 @@ als_zeit = zeit_normalisiert # lokaler Name beibehalten, Funktion lebt jetzt in
|
||||
|
||||
|
||||
def _fahrtfenster(
|
||||
zuendung_verlauf: list[Verlaufspunkt], pausenzeit_s: int
|
||||
zuendung_verlauf: list[Verlaufspunkt],
|
||||
) -> list[tuple[datetime.datetime, datetime.datetime]]:
|
||||
"""Aus dem Zündungsverlauf die Zeiträume, in denen gefahren wurde.
|
||||
|
||||
Zwei Schritte, die zusammen die Pausenregel aus §7.1 nachbilden: erst
|
||||
jeden zusammenhängenden "on"-Abschnitt sammeln, dann benachbarte
|
||||
Abschnitte verschmelzen, deren Lücke kürzer als die Pausenzeit ist. Genau
|
||||
das tut die Live-Erkennung über die abbrechbare Wartezeit, nur eben im
|
||||
Nachhinein und ohne Warten."""
|
||||
Jeder zusammenhängende "on"-Abschnitt ist eine Fahrt. Bis 2026-08-31
|
||||
wurden benachbarte Abschnitte hier zusätzlich verschmolzen, wenn ihre
|
||||
Lücke kürzer war als eine Pausenzeit - das bildete die damalige
|
||||
Live-Erkennung nach. Beide sind entfallen, weil der FMM003 die Regel
|
||||
selbst anwendet ("Ignition OFF Timeout" = 900 s): was im Verlauf als
|
||||
getrennte Abschnitte ankommt, hat das Gerät bereits als getrennte
|
||||
Fahrten beurteilt."""
|
||||
roh: list[tuple[datetime.datetime, datetime.datetime]] = []
|
||||
offen: datetime.datetime | None = None
|
||||
for ts, wert in zuendung_verlauf:
|
||||
@@ -106,17 +109,7 @@ def _fahrtfenster(
|
||||
# dem gewählten Fenster. Sie beim Fensterende abzuschneiden würde eine
|
||||
# Fahrt mit erfundener Endzeit erzeugen.
|
||||
|
||||
if not roh:
|
||||
return []
|
||||
|
||||
verschmolzen = [roh[0]]
|
||||
for start, ende in roh[1:]:
|
||||
vorheriger_start, vorheriges_ende = verschmolzen[-1]
|
||||
if (start - vorheriges_ende).total_seconds() < pausenzeit_s:
|
||||
verschmolzen[-1] = (vorheriger_start, ende)
|
||||
else:
|
||||
verschmolzen.append((start, ende))
|
||||
return verschmolzen
|
||||
return roh
|
||||
|
||||
|
||||
def _ueberschneidet(
|
||||
@@ -147,10 +140,24 @@ def _ueberschneidet(
|
||||
return False
|
||||
|
||||
|
||||
def _vmax(
|
||||
verlauf: list[Verlaufspunkt],
|
||||
start: datetime.datetime,
|
||||
ende: datetime.datetime,
|
||||
) -> float | None:
|
||||
"""Höchstgeschwindigkeit im Fahrtfenster, unplausible Werte verworfen -
|
||||
dieselbe Grenze und dieselbe Begründung wie beim Durchschnitt oben und
|
||||
wie im Live-Weg (screening._vmax_screenen())."""
|
||||
wert = hoechstwert_im_fenster(verlauf, start, ende)
|
||||
if wert is None or wert <= 0 or wert > UNPLAUSIBLE_KMH:
|
||||
return None
|
||||
return wert
|
||||
|
||||
|
||||
async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
"""Fahrten aus dem Zündungsverlauf, mit Kilometerstand und Start-/
|
||||
Zielkoordinaten aus den übrigen Verläufen ergänzt."""
|
||||
fenster = _fahrtfenster(verlaeufe["zuendung"], await pausenzeit_sekunden(k))
|
||||
fenster = _fahrtfenster(verlaeufe["zuendung"])
|
||||
if not fenster:
|
||||
return {"angelegt": 0, "uebersprungen": 0, "zu_kurz": 0}
|
||||
|
||||
@@ -159,6 +166,7 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
lat_verlauf = verlaeufe["lat"]
|
||||
lon_verlauf = verlaeufe["lon"]
|
||||
tank_liter_verlauf = verlaeufe["tank_liter"]
|
||||
tempo_verlauf = verlaeufe["tempo"]
|
||||
|
||||
angelegt = uebersprungen = zu_kurz = 0
|
||||
neue: list[dict] = []
|
||||
@@ -197,6 +205,9 @@ async def _fahrten_importieren(k: Koordinator, verlaeufe: dict) -> dict:
|
||||
"odo_start": odo_start,
|
||||
"odo_end": odo_end,
|
||||
"avg_speed_kmh": durchschnitt,
|
||||
# Der größte gemeldete Wert im Fahrtfenster, nicht die
|
||||
# tatsächliche Spitze - siehe hoechstwert_im_fenster().
|
||||
"vmax_kmh": _vmax(tempo_verlauf, f_start, f_ende),
|
||||
"start_lat": wert_bei(lat_verlauf, f_start),
|
||||
"start_lon": wert_bei(lon_verlauf, f_start),
|
||||
"end_lat": wert_bei(lat_verlauf, f_ende),
|
||||
@@ -439,6 +450,7 @@ async def importieren(k: Koordinator, start: object, ende: object) -> None:
|
||||
"aussentemp": await verlauf_lesen(k.hass, werte.AUSSENTEMP_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),
|
||||
"tempo": await verlauf_lesen(k.hass, werte.GESCHWINDIGKEIT_SENSOR, von, bis),
|
||||
}
|
||||
|
||||
# Frühester Zeitpunkt, zu dem im gewählten Fenster überhaupt etwas
|
||||
|
||||
Reference in New Issue
Block a user