Files
audi-app/tests/batterie/test_pruefen.py
T
tobias 7bbd236a17 Batteriehistorie: stehende Werte gelten nicht mehr als frische Messungen
Der Vorschlag aus Abschnitt CB (CAN-Wert innerhalb 300 s verlangen) wurde
gemessen und trifft nicht: er haette zwei eindeutige Werkbank-Tage
durchgelassen (CAN 5 Minuten neben dem Minimum) und echte Fahrzeugtage
verworfen - am stehenden Fahrzeug schweigt CAN, eine CAN-Frist verwirft also
ausgerechnet die Ruhespannung.

Der wirkliche Mechanismus: zustand_oder_none() liefert den zuletzt bekannten
Zustand unabhaengig vom Alter, und pruefen() stempelte ihn mit "jetzt". Ein
schweigender Dongle "misst" damit alle fuenf Minuten weiter seinen letzten
Wert. Am Bestand belegt: der 16.08.2026 trug einen Tageseintrag, obwohl der
Sensor an dem Tag kein einziges Mal gemeldet hatte (naechste echte Meldung 885
Minuten entfernt); sieben von 19 Eintraegen standen mehr als fuenf Minuten von
jeder echten Meldung entfernt.

messzeitpunkt() beantwortet jetzt beide Fragen an einer Stelle: ist das eine
neue Messung, und wann wurde sie gemacht. Gestempelt wird mit der Geraetezeit,
wenn zuordenbar - so wie der Rueckblick es laengst tut, der damit die richtige
Umsetzung war und keine Aenderung brauchte. Der Tag kommt aus der Messung, nicht
aus date.today(). Die Frischepruefung steht vor der Plausibilitaetsgrenze, sonst
warnt ein offline gegangener Dongle alle fuenf Minuten ueber denselben Wert (77
identische Zeilen in 3000 Protokollzeilen gezaehlt).

Die erste Fassung mass das Alter an last_updated und hatte damit ein Loch, das
erst die Live-Pruefung zeigte: Home Assistant setzt beim Hochfahren jede
Entitaet neu, last_updated steht danach auf der Neustartzeit. Gemessen: nach dem
Neustart um 21:12:54 trug der Spannungssensor last_updated 21:12:54, waehrend
die Meldezeit desselben Datensatzes 14:46:47 sagte. Das Alter wird deshalb an
der Messzeit gemessen.

Geprueft: 18 Tests in tests/batterie/, davon fallen 16 gegen HEAD; alle 116
Backend-Tests gruen; live im Testcontainer belegt - der erste Takt nach dem
Neustart meldet "keine neue Messung" statt der bisherigen Warnung.

Offen: Werkbank und Fahrzeug lassen sich aus diesen Daten nicht sicher trennen.
Abgestellt sind die erfundenen Eintraege, nicht die Frage, wo der Dongle steckt.
2026-09-06 23:30:08 +02:00

134 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""pruefen(): was tatsächlich in der Tagesliste landet - und was nicht.
Ergänzt test_messzeitpunkt.py um den ganzen Weg: der Helfer entscheidet, diese
Tests belegen, dass die Entscheidung auch ankommt.
Zwei Dinge stehen hier auf dem Prüfstand, beide am Bestand des Testcontainers
gemessen (06.09.2026):
1. Eine veraltete Messung darf keinen Eintrag erzeugen. Der 16.08.2026 trug
einen Tageseintrag, obwohl der Spannungssensor an diesem Tag kein einziges
Mal gemeldet hatte.
2. Sie darf auch nicht alle fünf Minuten dieselbe Warnung erzeugen. Im
laufenden Container standen 77 identische Zeilen "Batteriespannung 0.0 V
unter der Plausibilitätsgrenze" in 3000 Protokollzeilen - über einen Wert,
der sich seit 5½ Stunden nicht gerührt hatte. Deshalb kommt die
Frischeprüfung VOR der Plausibilitätsgrenze.
Aufruf: python3 tests/batterie/test_pruefen.py
(braucht das homeassistant-Paket, weil die Integration es importiert - also
z. B. im Test-Container)
"""
import asyncio
import datetime
import logging
import os
import sys
import unittest
_HIER = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(os.path.dirname(_HIER)))
from custom_components.audi_dashboard import batterie as b # noqa: E402
SPANNUNGSSENSOR = "sensor.spannung"
class Zustand:
def __init__(self, last_updated, state=""):
self.last_updated = last_updated
self.state = state
class Zustaende:
def __init__(self, daten):
self._daten = daten
def get(self, entity_id):
return self._daten.get(entity_id)
class Hass:
def __init__(self, daten):
self.states = Zustaende(daten)
class Werte:
BATTERIE_SENSOR = SPANNUNGSSENSOR
AUSSENTEMP_SENSOR = ""
MELDEZEIT_SENSOR = ""
class Zuordnung:
def __init__(self):
self.werte = Werte()
class Ablage:
def __init__(self):
self.eintraege = []
async def batterieverlauf_tageswert_aktualisieren(self, tag, ts, spannung, temp):
self.eintraege.append({"tag": tag, "ts": ts, "spannung": spannung, "temp": temp})
class Koordinator:
def __init__(self, alter_s, spannung):
gemessen = datetime.datetime.now(datetime.UTC) - datetime.timedelta(seconds=alter_s)
self.gemessen = gemessen
self.hass = Hass({SPANNUNGSSENSOR: Zustand(gemessen, str(spannung))})
self.zuordnung = Zuordnung()
self.ablage = Ablage()
self.veroeffentlicht = 0
async def batterieverlauf_veroeffentlichen(self):
self.veroeffentlicht += 1
def lauf(k):
asyncio.run(b.pruefen(k))
return k
class WasErfasstWird(unittest.TestCase):
def test_frische_messung_landet_mit_ihrer_eigenen_zeit(self):
k = lauf(Koordinator(alter_s=12, spannung=12.4))
self.assertEqual(len(k.ablage.eintraege), 1)
e = k.ablage.eintraege[0]
# Der entscheidende Unterschied zum alten Stand: nicht "jetzt", sondern
# der Zeitpunkt der Messung - und der Tag wird aus ihm abgeleitet.
self.assertEqual(e["ts"], k.gemessen.isoformat())
self.assertEqual(e["tag"], k.gemessen.date().isoformat())
self.assertEqual(e["spannung"], 12.4)
self.assertEqual(k.veroeffentlicht, 1)
def test_veraltete_messung_landet_gar_nicht(self):
"""Der Fall 16.08.2026 - Eintrag ohne eine einzige Meldung an dem Tag."""
k = lauf(Koordinator(alter_s=885 * 60, spannung=13.024))
self.assertEqual(k.ablage.eintraege, [])
self.assertEqual(k.veroeffentlicht, 0)
def test_unplausible_messung_landet_nicht(self):
k = lauf(Koordinator(alter_s=12, spannung=0.0))
self.assertEqual(k.ablage.eintraege, [])
class WieOftGewarntWird(unittest.TestCase):
def test_frische_unplausible_messung_warnt(self):
with self.assertLogs("custom_components.audi_dashboard.batterie", level="DEBUG") as p:
lauf(Koordinator(alter_s=12, spannung=0.0))
self.assertTrue(any(r.levelno == logging.WARNING for r in p.records))
def test_veraltete_unplausible_messung_warnt_nicht(self):
"""Sonst steht dieselbe Zeile alle fünf Minuten im Protokoll."""
with self.assertLogs("custom_components.audi_dashboard.batterie", level="DEBUG") as p:
lauf(Koordinator(alter_s=5 * 3600, spannung=0.0))
self.assertFalse(any(r.levelno >= logging.WARNING for r in p.records))
if __name__ == "__main__":
unittest.main(verbosity=2)