Regler stellt den Schlaf-Timeout des Dongles (2026.9.3.10)

Der Regler "Fahrt beenden" ist jetzt eine Zahl fuer zwei Dinge: unsere eigene
Wartezeit bis zum Fahrtende und der Schlaf-Timeout des FMM003 (103). So lange
bleibt der Dongle wach und wartet darauf, dass es gleich weitergeht; danach
schlaeft er. Die 180 s Ignition OFF Delay laufen davor und bleiben unsichtbar -
sie werden nur von der Fahrtdauer abgezogen. Der Regler geht deshalb von 1 bis
60 in Einerschritten; das Geraet kennt kein Timeout 0.

flespi.py liest und schreibt die Geraete-Konfiguration. Drei Annahmen sind an
der echten API gefallen und stehen als Messung im Modulkopf: der Wert liegt in
current (nicht value), die Einstellung heisst sleep_mode mit dem Wert eine Ebene
tiefer unter mode, und ein PUT braucht {"properties": ..., "address": ...}.

Die eigene Auftrags-Warteschlange ist wieder entfallen. Sie war fuer "das Geraet
schlaeft" gebaut - das gilt aber nur fuer das Geraet, nicht fuer die
Schnittstelle: die vollstaendige Konfiguration kam herein, waehrend das Fahrzeug
seit dem Vortag stand. flespi puffert selbst (pending + address=connection), ein
zweites Auftragsbuch waere eine zweite Warteschlange fuer dieselbe Aufgabe.

Gegengelesen wird nach jedem Schreiben; als Erfolg zaehlt current ODER pending -
letzteres ist bei stehendem Fahrzeug der Normalfall.

Live gegen den echten Account belegt: Token sieht genau ein Geraet, 127
Einstellungen bei parkendem Fahrzeug gelesen, Regler 15 geschrieben und als
pending bestaetigt (Nachbarfelder unveraendert), zweiter Neustart ohne erneute
Anfrage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 20:00:39 +02:00
parent 8c96d9b78e
commit 4d4890034a
18 changed files with 999 additions and 30 deletions
@@ -50,6 +50,7 @@ from homeassistant.helpers.storage import Store
from . import (
batterie,
fahrterkennung,
flespi,
identitaet,
reifen,
screening,
@@ -145,6 +146,20 @@ class Koordinator:
# liest diesen Wert bei jedem Takt erneut mit, damit er nicht
# zwischen zwei Prüfungen verschwindet.
self.letzte_update_pruefung: dict | None = None
# Die Reglerstellung "Fahrt beenden" in Minuten, aus dem Profil
# mitgelesen. Sie ist zugleich der Sollwert fuer den Schlaf-Timeout
# des Dongles (103) - siehe flespi.py. None = noch nicht gelesen.
self.pausenzeit_min: int | None = None
# Der zuletzt gelesene Stand der Geraete-Konfiguration. Ueberlebt einen
# Neustart, damit die Anzeige nicht auf "unbekannt" zurueckfaellt,
# obwohl sie erst eine Stunde alt ist - und damit wunsch_uebernehmen()
# ohne Netzabfrage weiss, ob ueberhaupt etwas zu tun ist. Was noch zum
# Geraet unterwegs ist, steht darin als "offen" (siehe flespi.py);
# eine eigene Warteschlange gibt es bewusst nicht.
self.flespi_stand: dict | None = None
# Zwei Wege koennen gleichzeitig schreiben wollen (Profil gespeichert,
# Knopf gedrueckt) - der zweite haette nichts mehr zu tun.
self.flespi_sperre = asyncio.Lock()
self._takte: list[CALLBACK_TYPE] = []
self._beobachter: list[CALLBACK_TYPE] = []
@@ -198,6 +213,10 @@ class Koordinator:
# Fahrt warten. Reicht das Ortsbudget eines Laufs nicht, plant das
# Screening selbst einen zweiten ein (screening._nachfassen_planen).
await screening.durchfuehren(self)
# Weicht die Reglerstellung vom zuletzt gelesenen Geraetewert ab,
# wird die Aenderung hier vorgemerkt - auch wenn sie waehrend eines
# Ausfalls gesetzt wurde oder ein frueherer Auftrag nie zum Zug kam.
await flespi.wunsch_uebernehmen(self)
async def beenden(self) -> None:
if self.screening_nachlauf is not None:
@@ -385,6 +404,7 @@ class Koordinator:
)
geladen = None
self.geparkt_seit_ts = geladen
self.flespi_stand = gespeichert.get("flespi_stand")
async def _laufzeit_sichern(self) -> None:
await self._store.async_save({
@@ -394,8 +414,14 @@ class Koordinator:
"geparkt_seit_ts": (
self.geparkt_seit_ts.isoformat() if self.geparkt_seit_ts else None
),
"flespi_stand": self.flespi_stand,
})
async def flespi_stand_setzen(self, stand: dict | None) -> None:
self.flespi_stand = stand
await self._laufzeit_sichern()
await self.app_version_veroeffentlichen()
async def fahrt_start_setzen(self, ts: datetime.datetime | None) -> None:
self.fahrt_start_ts = ts
await self._laufzeit_sichern()
@@ -501,6 +527,15 @@ class Koordinator:
if profil is None:
return
self.setzen(E_PROFIL, "aktuell", profil)
# Die Reglerstellung mitlesen: sie ist der Sollwert für den
# Schlaf-Timeout des Dongles, und flespi.py hat keinen eigenen
# Zugriff auf das Profil.
try:
self.pausenzeit_min = int(
(profil.get("einstellungen") or {})["fahrten_pausenzeit_min"]
)
except (KeyError, TypeError, ValueError):
self.pausenzeit_min = None
# Die drei Reifen-Kontrollwerte hängen am selben Profil - zusammen
# veröffentlichen, damit sie nicht auseinanderlaufen können.
# Die beiden km-Stände bleiben Zahlen (die Entitäten tragen die
@@ -595,6 +630,12 @@ class Koordinator:
# der Token selbst verlaesst die Integration nie, siehe
# zugaenge.py.
"zugaenge": zugaenge.stand(self),
# Was am Dongle steht, was noch aussteht, und wie alt die
# Angabe ist - siehe flespi.py.
"dongle": {
"aktiv": flespi.zugang(self) is not None,
"stand": self.flespi_stand,
},
},
)