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).
108 lines
4.5 KiB
Python
108 lines
4.5 KiB
Python
"""Reifenzähler ohne utility_meter (§7.5, §10 Punkt 6).
|
|
|
|
ABWEICHUNG VOM URSPRÜNGLICHEN LASTENHEFT (Frontend-Anpassung, nach dem
|
|
ursprünglichen Bau nachträglich verlangt): früher war der Zähler = aktueller
|
|
Kilometerstand - fester Startwert je Satz (reifen.saetze.<satz>.
|
|
start_odo_km). Das war falsch, sobald einmal zwischen Sommer und Winter
|
|
umgeschaltet wurde: die Differenz zum Startwert zählt dann alle seither
|
|
gefahrenen Kilometer mit, auch die auf dem jeweils anderen Satz. Jetzt gilt
|
|
stattdessen: jeder gefahrene Kilometer wird dem Satz zugerechnet, der zum
|
|
Zeitpunkt der Fahrt aktiv war. Dafür führt reifen.saetze.<satz>.km selbst den
|
|
fortlaufenden Gesamtstand (statt eines Startwerts), und reifen.
|
|
referenz_odo_km hält den zuletzt verarbeiteten Kilometerstand fest, gegen den
|
|
die nächste Änderung als Delta gemessen wird. start_odo_km/§13 entfällt
|
|
damit vollständig.
|
|
|
|
Ergebnis wird zusätzlich als eigene pyscript-Zustandsvariable bereitgestellt
|
|
(nur zur Kontrolle in Entwicklerwerkzeuge → Zustände, das Frontend liest die
|
|
Werte direkt aus dem veröffentlichten Profil):
|
|
pyscript.reifen_sommer_km, pyscript.reifen_winter_km,
|
|
pyscript.reifen_aktiver_satz
|
|
|
|
Kein Abhängigkeit von total_increasing oder utility_meter-internem Zustand -
|
|
die Fortschreibung braucht nur den zuletzt bekannten Absolutwert des
|
|
Kilometerstand-Sensors. Ein zeitweise `unavailable` Quellsensor (§4.1)
|
|
verliert dadurch nichts: referenz_odo_km bleibt einfach stehen, bis wieder
|
|
ein gültiger Wert kommt, und das nächste Delta wird dann gegen diesen alten
|
|
Stand berechnet - auch km, die während eines HA-Neustarts/Ausfalls gefahren
|
|
wurden, werden so noch dem damals aktiven Satz zugerechnet.
|
|
|
|
Entity-IDs stehen zentral in pyscript/modules/einstellungen.py.
|
|
"""
|
|
|
|
import einstellungen
|
|
import frontend_veroeffentlichung
|
|
import profil
|
|
|
|
|
|
def _aktueller_km():
|
|
try:
|
|
return float(frontend_veroeffentlichung.zustand_oder_none(einstellungen.KM_SENSOR))
|
|
except (TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
def _zustaende_veroeffentlichen(p):
|
|
reifen = p.get("reifen", {})
|
|
saetze = reifen.get("saetze", {})
|
|
for satz_name in ("sommer", "winter"):
|
|
km = saetze.get(satz_name, {}).get("km")
|
|
if km is None:
|
|
state.set(f"pyscript.reifen_{satz_name}_km", "unbekannt")
|
|
else:
|
|
state.set(f"pyscript.reifen_{satz_name}_km", km, new_attributes={"unit_of_measurement": "km"})
|
|
state.set("pyscript.reifen_aktiver_satz", reifen.get("aktiv", "unbekannt"))
|
|
|
|
|
|
def _fortschreiben_und_veroeffentlichen(neuer_km, neuer_aktiv=None):
|
|
"""Rechnet zuerst mit dem bisher aktiven Satz ab (Delta seit
|
|
referenz_odo_km), trägt danach optional einen Satzwechsel ein - in dieser
|
|
Reihenfolge, damit beim Wechseln selbst keine km dem neuen Satz
|
|
zugerechnet werden, die eigentlich noch auf dem alten gefahren wurden."""
|
|
p = profil.profil_lesen()
|
|
if p is None:
|
|
return
|
|
reifen = p.setdefault("reifen", {})
|
|
saetze = reifen.setdefault("saetze", {})
|
|
aktiv = reifen.get("aktiv", "sommer")
|
|
referenz = reifen.get("referenz_odo_km")
|
|
|
|
if referenz is not None and neuer_km is not None:
|
|
delta = round(neuer_km - referenz, 1)
|
|
if delta > 0:
|
|
satz = saetze.setdefault(aktiv, {})
|
|
satz["km"] = round((satz.get("km") or 0) + delta, 1)
|
|
if neuer_km is not None:
|
|
reifen["referenz_odo_km"] = neuer_km
|
|
if neuer_aktiv is not None:
|
|
reifen["aktiv"] = neuer_aktiv
|
|
|
|
profil.profil_schreiben(p)
|
|
frontend_veroeffentlichung.profil_veroeffentlichen()
|
|
_zustaende_veroeffentlichen(p)
|
|
|
|
|
|
@time_trigger("startup")
|
|
def beim_start():
|
|
_fortschreiben_und_veroeffentlichen(_aktueller_km())
|
|
|
|
|
|
# Ohne konfigurierten Kilometerstand-Sensor keinen Trigger registrieren -
|
|
# eine leere Entity-ID als @state_trigger-Ausdruck ist ungetestetes
|
|
# Verhalten, siehe gleiches Muster in fahrterkennung.py.
|
|
if einstellungen.KM_SENSOR:
|
|
@state_trigger(f"{einstellungen.KM_SENSOR}")
|
|
def kilometerstand_geaendert(value=None):
|
|
_fortschreiben_und_veroeffentlichen(_aktueller_km())
|
|
|
|
|
|
@service
|
|
def audi_dashboard_reifen_wechseln(satz=None):
|
|
"""Umschalten des aktiven Reifensatzes (§5.2). Aufruf als
|
|
pyscript.audi_dashboard_reifen_wechseln mit satz="sommer"|"winter"."""
|
|
if satz not in ("sommer", "winter"):
|
|
log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'")
|
|
return
|
|
_fortschreiben_und_veroeffentlichen(_aktueller_km(), neuer_aktiv=satz)
|
|
log.info(f"audi_dashboard: aktiver Reifensatz auf '{satz}' umgeschaltet")
|