"""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.. 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..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") @service def audi_dashboard_reifen_km_setzen(satz=None, km=None): """Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes (Editier-Symbol neben der km-Anzeige in der App). Überschreibt nur den gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste Fortschreibung (kilometerstand_geaendert oben) weiterhin korrekt von hier aus weiterzählt, statt auf den neuen Wert einen erneuten Delta seit dem alten Stand aufzuschlagen. configCarZuProfil() im Frontend schreibt km nie selbst mit (siehe Kommentar dort) - genau deshalb braucht es diesen eigenen Service statt eines einfachen Profil-Überschreibens.""" if satz not in ("sommer", "winter"): log.warning(f"audi_dashboard: ungültiger Reifensatz '{satz}'") return try: km = round(float(km), 1) except (TypeError, ValueError): log.warning(f"audi_dashboard: ungültiger km-Wert '{km}'") return if km < 0: log.warning(f"audi_dashboard: negativer km-Wert '{km}' abgelehnt") return p = profil.profil_lesen() if p is None: return reifen = p.setdefault("reifen", {}) saetze = reifen.setdefault("saetze", {}) saetze.setdefault(satz, {})["km"] = km profil.profil_schreiben(p) frontend_veroeffentlichung.profil_veroeffentlichen() _zustaende_veroeffentlichen(p) log.info(f"audi_dashboard: km-Stand '{satz}' manuell auf {km} gesetzt")