Batteriespannung-Fallback: zeigt letzte Ruhespannungsmessung ueber Neustarts hinweg

Der RAM-only Fallback fuer die Zustand-Kachel wurde bei jedem Neustart
geleert und zeigte "unbekannt", solange seit dem Neustart keine echte
Live-Messung eintraf (Dongle bei ausgeschaltetem Fahrzeug erwartungsgemaess
immer offline). spannung_cache_vorladen() laedt ihn jetzt beim Start aus
der gespeicherten Historie vor, begrenzt auf den vereinbarten Bereich
10,0-13,0 V (SPANNUNG_MIN_V bis AGM_RUHE_MAX_V) - eine Generatorspannung
(z.B. 15,4 V) soll dort nie als Batteriespannung erscheinen.

Version 2026.8.27.21, im Testcontainer verifiziert (12,149 V statt
"unbekannt", trotz Dongle weiterhin 0 V live).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 00:23:16 +02:00
parent 90fff7b305
commit f4222b715f
5 changed files with 126 additions and 11 deletions
@@ -61,6 +61,20 @@ _LOGGER = logging.getLogger(__name__)
# Moduldocstring).
SPANNUNG_MIN_V = 10.0
# Obergrenze für eine echte Ruhespannungsmessung (vom Nutzer festgelegt: "max
# voltage for AGM Battery is 13V, everything above is the generator") -
# darüber ist es Generator-/Ladespannung, keine Batteriespannung mehr.
# Identisch mit AGM_RUHE_MAX_V im Frontend (audi-dashboard-app.js/
# Batterie.tsx), dort aber nur für die Anzeige (welche Tage ins Ladezustands-
# /Trend-Diagramm einfließen - siehe Moduldocstring oben, "erst beim Anzeigen
# angewendet"). Hier zusätzlich fürs Backend gebraucht, als oberer Rand für
# spannung_cache_vorladen() (veroeffentlichung.py): der "zuletzt gemessene
# Wert"-Fallback für die Zustand-Kachel soll ausdrücklich innerhalb von
# [SPANNUNG_MIN_V, AGM_RUHE_MAX_V] bleiben - eine Generatorspannung soll dort
# nie als "Batteriespannung" erscheinen, auch nicht als letzter bekannter
# Wert.
AGM_RUHE_MAX_V = 13.0
async def pruefen(k: Koordinator) -> None:
sensor = k.zuordnung.werte.BATTERIE_SENSOR
@@ -67,7 +67,7 @@ from .const import (
E_ZUORDNUNG,
SIGNAL_AKTUALISIERT,
)
from .veroeffentlichung import fahrzeugstatus
from .veroeffentlichung import fahrzeugstatus, spannung_cache_vorladen
from .zuordnung import Zuordnung
_LOGGER = logging.getLogger(__name__)
@@ -126,6 +126,12 @@ class Koordinator:
)
await self.zuordnung.anwenden()
await self._laufzeit_laden()
# Die "Zustand"-Kachel soll die zuletzt gemessene Batteriespannung
# auch direkt nach einem Neustart zeigen, nicht erst "unbekannt" bis
# zur nächsten Live-Messung (siehe spannung_cache_vorladen()) - vor
# dem ersten alles_veroeffentlichen(), damit schon dessen erste
# Veröffentlichung den vorgeladenen Wert trägt.
spannung_cache_vorladen(await self.ablage.batterieverlauf_lesen())
await self.alles_veroeffentlichen()
# Der Reifenzähler schreibt beim Start einmal fort: Kilometer, die
# während eines HA-Ausfalls gefahren wurden, gehören noch dem damals
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.8.27.19",
"version": "2026.8.27.21",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": ["@paul"],
@@ -27,15 +27,58 @@ from .einstellungen import POSITIONEN, Sensorzuordnung
# Letzte plausible Batteriespannung, für die "Zustand"-Kachel, wenn die
# aktuelle Live-Messung gerade unplausibel ist (Sensor kurzzeitig offline,
# Dongle getrennt o.ä.) - der Nutzer will dort die zuletzt gemessene Spannung
# sehen statt "unbekannt". RAM-only wie andere Laufzeitzustände dieses
# Projekts (siehe die bekannten Lücken zu Fahrtstart/Tankstand): kein Store,
# bewusst - ein Neustart zeigt kurz wieder "unbekannt", bis die nächste echte
# Messung eintrifft, statt einen möglicherweise sehr alten Wert aus einer
# Datei wiederzubeleben.
# Dongle getrennt o.ä. - erwartungsgemäß bei ausgeschaltetem Fahrzeug immer)
# - der Nutzer will dort die zuletzt gemessene Spannung sehen statt
# "unbekannt", ohne Ablaufdatum. Im RAM statt in einem Store wie andere
# Laufzeitzustände dieses Projekts (siehe die bekannten Lücken zu
# Fahrtstart/Tankstand) - aber koordinator.starten() lädt diesen Wert bei
# jedem Neustart einmalig aus der gespeicherten Historie vor
# (spannung_cache_vorladen()), bevor die erste Veröffentlichung läuft, damit
# ein Neustart NICHT mehr kurz "unbekannt" zeigt.
_letzte_gueltige_spannung: dict[str, float | None] = {"wert": None}
def spannung_cache_vorladen(verlauf: list[dict]) -> None:
"""Lädt die "Zustand"-Kachel-Fallback-Spannung beim Start aus der
gespeicherten Historie (batteriespannung.jsonl) vor, statt bis zur
nächsten echten Live-Messung nach jedem Neustart erst "unbekannt" zu
zeigen.
Der Dongle ist bei ausgeschaltetem Fahrzeug erwartungsgemäß immer
offline (Nutzerangabe) - "die zuletzt gemessene Spannung" soll deshalb
auch über einen Neustart hinweg sichtbar bleiben, und zwar ohne
Ablaufdatum: hier gibt es bewusst KEINE Prüfung, wie alt der Wert ist -
ein alter, aber echter Messwert ist der Kachel immer noch lieber als
"unbekannt".
Aber nur innerhalb der vereinbarten Grenze [SPANNUNG_MIN_V,
AGM_RUHE_MAX_V] (10,0-13,0 V, batterie.py): min/max werden roh
gespeichert (siehe batterie.py's Moduldocstring) und können deshalb auch
Generatorspannung enthalten (z. B. ein Tagesmaximum von 15,4 V bei
laufendem Alternator) - so ein Wert soll nie als "Batteriespannung"
auf der Zustand-Kachel landen, auch nicht als letzter bekannter Wert.
Durchsucht deshalb die GESAMTE Historie (nicht nur den letzten
Tageseintrag) nach dem zeitlich jüngsten min- oder max-Wert, der
innerhalb der Grenze liegt - ein Tag, an dem das Fahrzeug nie im
Ruhezustand beobachtet wurde (beide Werte über der Grenze), wird dabei
übersprungen, der davor gemessene gültige Wert bleibt der Fallback."""
from .batterie import AGM_RUHE_MAX_V, SPANNUNG_MIN_V
kandidaten: list[tuple[str, float]] = []
for eintrag in verlauf:
for wert_feld, ts_feld in (("min", "min_ts"), ("max", "max_ts")):
wert = eintrag.get(wert_feld)
ts = eintrag.get(ts_feld)
if wert is None or not ts:
continue
if SPANNUNG_MIN_V <= wert <= AGM_RUHE_MAX_V:
kandidaten.append((ts, wert))
if not kandidaten:
return
kandidaten.sort(key=lambda p: p[0])
_letzte_gueltige_spannung["wert"] = kandidaten[-1][1]
def zustand_oder_none(hass: HomeAssistant, entity_id: str | None) -> str | None:
"""Sicherer Zustandszugriff.