Eine Neuschreibung ist kein Wechsel - Wartezeit startet nicht mehr neu

Home Assistant schreibt denselben Zustandswert auch neu, wenn sich nur ein
Attribut geruehrt hat. Am 04.09.2026 gezaehlt: von 22 Zustandszeilen der
Zuendung waren nur 6 echte Wechsel, die uebrigen 16 Neuschreibungen - und
alle 6 Wechsel lagen ausserhalb der Fahrten. Das Zuendungssignal ist
unterwegs stabil.

Der Beobachter unterschied das nicht. Fiel eine Neuschreibung von "aus" in
die laufende Wartezeit, brach Zweig 2 die wartende Bestaetigung ab und
begann die 15 Minuten von vorn. Am 04.09. ist es nicht passiert - die
Neuschreibungen lagen hinter dem Fenster -, aber das Loch war da.

_signalwechsel() bekommt jetzt mit, ob es ein echter Wechsel war. Eine
Neuschreibung, waehrend fuer diese Fahrt schon eine Bestaetigung wartet,
laesst sie stehen.

Bewusst NICHT als Filter ganz oben: _spaetes_aus() verlaesst sich darauf,
ein Zuendungs-Aus auch als off -> off zu sehen - daran haengt das
Nachtragen vorlaeufiger Enden. Die Pruefung sitzt nur in Zweig 2, und ein
eigener Test belegt, dass der andere Weg weiter offen ist.

21 Tests (von 18), Gegenprobe mit ausgeschalteter Pruefung scheitert wie
erwartet an einem. py_compile sauber, audi_ha_test auf 2026.9.4.25 ohne
Traceback.

AGENTS.md haelt ausserdem fest: die Beschreibung des Beenden-Knopfes war zu
weit gefasst (die Korrektur zieht nur nach hinten, nie nach vorn), und der
flespi-Cache laesst sich ueber DELETE auf die Einstellung zum Neulesen
zwingen - mit dem Haken, dass dabei ausstehende Werte verloren gingen.
This commit is contained in:
2026-09-05 01:02:11 +02:00
parent 242312441d
commit 9ac69cdf44
6 changed files with 193 additions and 4 deletions
@@ -337,6 +337,74 @@ class GepufferterStapel(Basis):
self.assertEqual(k.beendet, [])
self.assertEqual(k.fahrt_start_ts, beginn)
class Neuschreibung(Basis):
"""Home Assistant schreibt denselben Wert auch neu.
Am 04.09.2026 gezaehlt: von 22 Zustandszeilen der Zuendung waren nur 6
echte Wechsel. Die uebrigen 16 waren Neuschreibungen - der Wert selbst
hatte sich nicht geruehrt.
Der Beobachter unterscheidet das nicht, er reicht neu und alt durch. Faellt
so eine Neuschreibung von "aus" in die laufende Wartezeit, begann die
Wartezeit von vorn - und bei genuegend vielen nie ein Ende.
"""
async def test_neuschreibung_verlaengert_die_wartezeit_nicht(self):
k = FakeKoordinator()
await f.zuendung_geaendert(k, "off", "on", AUS)
wartet = k.ende_wartet
self.assertIsNotNone(wartet, "nach dem Aus wartet eine Bestaetigung")
# Dieselbe Meldung noch einmal, ohne Wertwechsel.
await f.zuendung_geaendert(k, "off", "off", AUS + datetime.timedelta(seconds=30))
self.assertIs(
k.ende_wartet, wartet, "es wartet weiterhin dieselbe Bestaetigung"
)
await k.ausklingen()
self.assertEqual(len(k.beendet), 1, "und sie kommt zum Zug")
self.assertEqual(
k.beendet[0][1],
AUS - datetime.timedelta(seconds=f.ZUENDUNG_NACHLAUF_S),
"mit dem Ende aus dem ERSTEN Aus, nicht dem der Neuschreibung",
)
async def test_echtes_zweites_aus_zaehlt_weiterhin(self):
"""Die Gegenprobe: ein Wechsel darf die Bestaetigung sehr wohl ersetzen."""
k = FakeKoordinator()
await f.zuendung_geaendert(k, "off", "on", AUS)
erstes = k.ende_wartet
await f.zuendung_geaendert(k, "on", "off", AUS + datetime.timedelta(seconds=30))
await f.zuendung_geaendert(k, "off", "on", AUS + datetime.timedelta(seconds=60))
self.assertIsNotNone(k.ende_wartet)
self.assertNotEqual(
k.ende_wartet, erstes, "ein echtes zweites Aus setzt ein neues Ende"
)
async def test_neuschreibung_ohne_laufende_fahrt_geht_weiter_durch(self):
"""_spaetes_aus() braucht auch ein off -> off.
Ein zweites, spaeter gepuffertes Aus traegt das Ende einer von Hand
beendeten Fahrt nach. Ein Filter ganz oben haette genau das
verschluckt - deshalb sitzt die Pruefung nur in Zweig 2.
"""
gesehen = []
async def spaetes_aus(k, ereigniszeit, jetzt):
gesehen.append(ereigniszeit)
echt = f._spaetes_aus
f._spaetes_aus = spaetes_aus
try:
k = FakeKoordinator(start_ts=None)
await f.zuendung_geaendert(k, "off", "off", AUS)
self.assertEqual(
len(gesehen), 1, "ohne laufende Fahrt muss das Aus ankommen"
)
finally:
f._spaetes_aus = echt
class Einstellung(unittest.IsolatedAsyncioTestCase):
"""_pausenzeit_s selbst - hier ungepatcht."""