163efe1392
Bringt die vollstaendig gebaute DataMetric360-App zusammen (React/Vite, 21 Screens, Audi-Assets, PWA + native Capacitor-Huelle, 90+9 Tests) sowie die profil_lesen()-Haertung gegen fehlende/kaputte Profildatei zusammen. Konfliktaufloesung: - AGENTS.md, INSTALL.md, README.md: main-Fassung war jeweils die chronologisch neuere, uebernommen und um die durch den Merge tatsaechlich erledigten Punkte ergaenzt (profil_lesen()-Haertung, Audit-Reste-Entscheidung). - fahrterkennung.py: toten WLAN-Zweig vom Branch verworfen, Zuendungs- basierte Erkennung von main behalten. - homeassistant/FMM003_MAPPING.md (MQTT/Mosquitto-Ansatz vom 2026-08-11, vor der Umstellung auf flespi) bewusst nicht uebernommen - main nutzt seit 2026-08-12 flespi als alleinigen FMM003-Datenweg. UMSETZUNGSPLAN.md Phase 13 entsprechend als ueberholt markiert, verweist auf AGENTS.md als massgeblich. - REVIEW_main_2026-08-13.md, ha_install.md (add/add): main-Fassung war die spaetere Revision derselben Dokumente, uebernommen. Die von REVIEW_main_2026-08-13.md befuerchtete Merge-Falle (profil_lesen() gibt jetzt None zurueck, main-seitige Aufrufer pruefen das nicht) wurde verifiziert als bereits entschaerft: alle 5 Aufrufstellen im gemergten Stand (backup.py x2, fahrterkennung.py, frontend_veroeffentlichung.py, reifenzaehler.py, tankerkennung.py) sind None-sicher. installationspaket/ nicht Teil dieses Commits (gitignored, wird bei Bedarf neu zusammengestellt).
124 lines
4.6 KiB
Python
124 lines
4.6 KiB
Python
"""Automatische Tankerkennung über den Füllstandssensor (Erweiterung zu §7.7/§5.5,
|
|
nach dem ursprünglichen Bau nachträglich verlangt).
|
|
|
|
Beobachtung des Nutzers: der Füllstand (TANK_SENSOR, in Prozent) springt beim
|
|
Fahren nie nach oben, er sinkt nur - jeder Anstieg ist also ein Tankvorgang.
|
|
Schwelle nach Nutzervorgabe: ab +5 Liter ODER +9 Prozentpunkten Anstieg gilt
|
|
als nachgetankt. Die 5-Liter-Vorgabe wird über fahrzeug.tankvolumen_liter aus
|
|
dem Fahrzeugprofil in Prozentpunkte umgerechnet, damit beide Angaben auf
|
|
derselben Einheit (Prozent) verglichen werden können; es gilt jeweils die
|
|
empfindlichere (kleinere) der beiden Schwellen.
|
|
|
|
Tiefststand-Tracking statt einfachem Vorher/Nachher-Vergleich: die
|
|
Integration liefert einen Tankvorgang oft in mehreren kleinen Schritten
|
|
(z. B. 40% -> 45% -> 60%), von denen keiner allein die Schwelle
|
|
überschreiten muss. Deshalb wird der zuletzt bekannte Tiefststand
|
|
gespeichert und der Anstieg dagegen gemessen, nicht gegen den unmittelbar
|
|
vorherigen Wert. Nach Anlage eines Tankvorgangs wird der Tiefststand auf den
|
|
aktuellen Wert zurückgesetzt, damit derselbe Vorgang nicht mehrfach neue
|
|
Datensätze erzeugt, wenn der Sensor währenddessen weiter ansteigt.
|
|
|
|
Der so angelegte Tankvorgang ist bewusst ein unvollständiger Platzhalter
|
|
(status "unvollständig", ohne Liter/Kosten/Station) - siehe §10 Punkt 5 der
|
|
letzten Rückmeldung: die Erwartung ist ein automatisch erkannter Tankvorgang
|
|
mit Zeitstempel, Kilometerstand und gefahrener Distanz, den der Nutzer später
|
|
per Beleg-Nachtrag (audi_dashboard_beleg_hochladen mit tank_id, siehe
|
|
belegverarbeitung.py) vervollständigt.
|
|
|
|
EINSCHRÄNKUNG: der Tiefststand (_tiefststand_pct) lebt nur im Arbeitsspeicher
|
|
dieses Skripts, analog zur bekannten Einschränkung in fahrterkennung.py
|
|
(_fahrt_start_ts). Ein HA-Neustart während oder kurz vor einem Tankvorgang
|
|
verliert den bisherigen Tiefststand - der nächste Anstieg wird dann gegen den
|
|
ersten nach dem Neustart gemeldeten Wert gemessen. Bewusst nicht gehärtet,
|
|
aus demselben Grund wie dort.
|
|
|
|
Entity-IDs stehen zentral in pyscript/modules/einstellungen.py.
|
|
"""
|
|
|
|
import datetime
|
|
|
|
import einstellungen
|
|
import frontend_veroeffentlichung
|
|
import profil
|
|
|
|
LITER_SCHWELLE = 5
|
|
PROZENT_SCHWELLE = 9
|
|
STANDARD_TANKVOLUMEN_LITER = 58 # Fallback, falls im Profil nicht hinterlegt
|
|
|
|
_tiefststand_pct = None
|
|
|
|
|
|
def _als_zahl(wert):
|
|
try:
|
|
return float(wert)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _schwelle_prozent():
|
|
p = profil.profil_lesen()
|
|
if p is None:
|
|
tankvolumen = STANDARD_TANKVOLUMEN_LITER
|
|
else:
|
|
tankvolumen = p.get("fahrzeug", {}).get("tankvolumen_liter") or STANDARD_TANKVOLUMEN_LITER
|
|
liter_als_prozent = (LITER_SCHWELLE / tankvolumen) * 100
|
|
return min(liter_als_prozent, PROZENT_SCHWELLE)
|
|
|
|
|
|
@time_trigger("startup")
|
|
def beim_start():
|
|
profil.ordner_sicherstellen()
|
|
|
|
|
|
# Ohne konfigurierten Tankfüllstand-Sensor keinen Trigger registrieren -
|
|
# eine leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes
|
|
# Verhalten, siehe gleiches Muster in fahrterkennung.py.
|
|
if einstellungen.TANK_SENSOR:
|
|
@state_trigger(f"{einstellungen.TANK_SENSOR}")
|
|
def tankfuellstand_geaendert(value=None, old_value=None):
|
|
global _tiefststand_pct
|
|
|
|
aktuell = _als_zahl(value)
|
|
if aktuell is None:
|
|
return
|
|
|
|
if _tiefststand_pct is None:
|
|
_tiefststand_pct = aktuell
|
|
return
|
|
|
|
if aktuell <= _tiefststand_pct:
|
|
_tiefststand_pct = aktuell
|
|
return
|
|
|
|
anstieg = aktuell - _tiefststand_pct
|
|
if anstieg >= _schwelle_prozent():
|
|
_tankvorgang_automatisch_anlegen(anstieg)
|
|
_tiefststand_pct = aktuell
|
|
|
|
|
|
def _tankvorgang_automatisch_anlegen(anstieg_pct):
|
|
odometer_km = frontend_veroeffentlichung.zustand_oder_none(einstellungen.KM_SENSOR)
|
|
tankvorgang = {
|
|
"tank_id": profil.neue_id("f"),
|
|
"receipt_key": None,
|
|
"ts": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
"liters": None,
|
|
"fuel_total_eur": None,
|
|
"price_per_l": None,
|
|
"discount": None,
|
|
"station_name": None,
|
|
"odometer_km": odometer_km,
|
|
"distance_km": profil.distanz_seit_letzter_tankung(odometer_km),
|
|
"fuel_type": None,
|
|
"source": "auto",
|
|
"status": "unvollständig",
|
|
"receipt_file": None,
|
|
"edited_fields": [],
|
|
}
|
|
profil.tankvorgang_anhaengen(tankvorgang)
|
|
frontend_veroeffentlichung.tankvorgaenge_veroeffentlichen()
|
|
log.info(
|
|
f"audi_dashboard: Tankvorgang {tankvorgang['tank_id']} automatisch erkannt "
|
|
f"(Füllstandsanstieg {round(anstieg_pct, 1)} Prozentpunkte)"
|
|
)
|