"""Reifenzähler: jeder gefahrene Kilometer wird dem Satz zugerechnet, der zum Zeitpunkt der Fahrt aktiv war. Früher war der Zähler = aktueller Kilometerstand minus fester Startwert je Satz. 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 führt reifen.saetze..km selbst den fortlaufenden Gesamtstand, und reifen.referenz_odo_km hält den zuletzt verarbeiteten Kilometerstand fest, gegen den die nächste Änderung als Delta gemessen wird. Keine Abhängigkeit von total_increasing oder utility_meter: die Fortschreibung braucht nur den zuletzt bekannten Absolutwert des Kilometerstand-Sensors. Ein zeitweise "unavailable" Quellsensor 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 Kilometer, die während eines HA-Ausfalls gefahren wurden, werden so noch dem damals aktiven Satz zugerechnet. """ from __future__ import annotations import logging from typing import TYPE_CHECKING from .veroeffentlichung import zustand_oder_none if TYPE_CHECKING: from .koordinator import Koordinator _LOGGER = logging.getLogger(__name__) SAETZE = ("sommer", "winter") def aktueller_km(k: Koordinator) -> float | None: try: return float(zustand_oder_none(k.hass, k.zuordnung.werte.KM_SENSOR)) # type: ignore[arg-type] except (TypeError, ValueError): return None async def fortschreiben(k: Koordinator, neuer_km: float | None, neuer_aktiv: str | None = None) -> 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 Kilometer dem neuen Satz zugerechnet werden, die eigentlich noch auf dem alten gefahren wurden.""" profil = await k.ablage.profil_lesen() if profil is None: return reifen = profil.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 await k.ablage.profil_schreiben(profil) await k.profil_veroeffentlichen(profil) async def wechseln(k: Koordinator, satz: str | None) -> None: if satz not in SAETZE: _LOGGER.warning("Ungültiger Reifensatz '%s'", satz) return await fortschreiben(k, aktueller_km(k), neuer_aktiv=satz) _LOGGER.info("Aktiver Reifensatz auf '%s' umgeschaltet", satz) async def km_setzen(k: Koordinator, satz: str | None, km: object) -> None: """Manuelle Korrektur des gefahrenen Kilometerstands eines Reifensatzes. Überschreibt nur den gespeicherten Zähler selbst - referenz_odo_km (der zuletzt verarbeitete Fahrzeug-Kilometerstand) bleibt unverändert, damit die nächste Fortschreibung weiterhin korrekt von hier aus weiterzählt, statt auf den neuen Wert erneut ein Delta seit dem alten Stand aufzuschlagen.""" if satz not in SAETZE: _LOGGER.warning("Ungültiger Reifensatz '%s'", satz) return try: wert = round(float(km), 1) # type: ignore[arg-type] except (TypeError, ValueError): _LOGGER.warning("Ungültiger km-Wert '%s'", km) return if wert < 0: _LOGGER.warning("Negativer km-Wert '%s' abgelehnt", wert) return profil = await k.ablage.profil_lesen() if profil is None: return reifen = profil.setdefault("reifen", {}) reifen.setdefault("saetze", {}).setdefault(satz, {})["km"] = wert await k.ablage.profil_schreiben(profil) await k.profil_veroeffentlichen(profil) _LOGGER.info("km-Stand '%s' manuell auf %s gesetzt", satz, wert) def zustaende(profil: dict | None) -> dict[str, object]: """Die drei Kontrollwerte für die eigenen Reifen-Entitäten. Das Frontend liest die Zahlen direkt aus dem veröffentlichten Profil - diese Entitäten sind nur zur Kontrolle in Entwicklerwerkzeuge -> Zustände und für eigene Automationen des Nutzers da.""" reifen = (profil or {}).get("reifen", {}) saetze = reifen.get("saetze", {}) return { "sommer": saetze.get("sommer", {}).get("km"), "winter": saetze.get("winter", {}).get("km"), "aktiv": reifen.get("aktiv") or "unbekannt", }