test(station): run_daemon selbst treiben statt eine Kopie der Schleife
Die Verdrahtung zwischen RefreshPlanner und Panel - Aktion auf Panel-Methode, was_full=upgraded, record_sleep(), Reihenfolge decide/ausfuehren/zurueckmelden - ueberlebte jede Mutation, weil der Test eine nachgebaute Schleife trieb. Am schlimmsten: der "full"-Zweig auf draw_partial() umgebogen haette bedeutet, dass es auf einem wachen Panel nie wieder einen echten Vollaufbau gibt. run_daemon() nimmt jetzt Uhr, Panel und Zustandsquelle entgegen (Vorgaben unveraendert) und wird im Test mit Fake-Uhr und protokollierendem Panel-Doppel direkt getrieben; die Nachbildung _run_schedule() entfaellt ersatzlos. Dazu: RefreshPlanner liest die Intervallkonstanten wieder pro Entscheidung statt sie als Default-Argument zur Importzeit zu binden, record_sleep() verliert den toten now-Parameter.
This commit is contained in:
@@ -328,10 +328,15 @@ class RefreshPlanner:
|
||||
_NO_MODE = object() # garantiert ungleich jedem echten Modus beim ersten Lauf
|
||||
|
||||
def __init__(self, now: float,
|
||||
full_refresh_interval_s: float = FULL_REFRESH_INTERVAL_S,
|
||||
sleep_after_idle_s: float = SLEEP_AFTER_IDLE_S) -> None:
|
||||
self.full_refresh_interval_s = full_refresh_interval_s
|
||||
self.sleep_after_idle_s = sleep_after_idle_s
|
||||
full_refresh_interval_s: float | None = None,
|
||||
sleep_after_idle_s: float | None = None) -> None:
|
||||
# Bewusst None statt der Modulkonstanten als Vorgabewert (Fixrunde 5): ein
|
||||
# Default-Argument wird zur Importzeit ausgewertet, ein spaeteres
|
||||
# monkeypatch.setattr(display, "FULL_REFRESH_INTERVAL_S", ...) haette dann keine
|
||||
# Wirkung mehr auf neu gebaute Planer - die alte Schleife las die Konstanten pro
|
||||
# Runde. Die Properties unten lesen sie weiterhin bei jeder Entscheidung.
|
||||
self._full_refresh_interval_s = full_refresh_interval_s
|
||||
self._sleep_after_idle_s = sleep_after_idle_s
|
||||
self.last_lines: list[str] | None = None
|
||||
self.last_mode: object = self._NO_MODE
|
||||
self.last_full = now
|
||||
@@ -363,6 +368,16 @@ class RefreshPlanner:
|
||||
self.partials_since_full = 0
|
||||
self.asleep = False
|
||||
|
||||
@property
|
||||
def full_refresh_interval_s(self) -> float:
|
||||
return (FULL_REFRESH_INTERVAL_S if self._full_refresh_interval_s is None
|
||||
else self._full_refresh_interval_s)
|
||||
|
||||
@property
|
||||
def sleep_after_idle_s(self) -> float:
|
||||
return (SLEEP_AFTER_IDLE_S if self._sleep_after_idle_s is None
|
||||
else self._sleep_after_idle_s)
|
||||
|
||||
def decide(self, now: float, lines: list[str], mode: object) -> str:
|
||||
"""Was jetzt zu tun ist. Aendert nichts - das Ergebnis wird per record_*()
|
||||
zurueckgemeldet."""
|
||||
@@ -395,7 +410,10 @@ class RefreshPlanner:
|
||||
else:
|
||||
self.partials_since_full += 1
|
||||
|
||||
def record_sleep(self, now: float) -> None:
|
||||
def record_sleep(self) -> None:
|
||||
"""Kein `now`-Parameter (Fixrunde 5): das Einschlafen verschiebt weder die
|
||||
30-Minuten-Uhr noch last_draw - es merkt sich nur, dass ein weiteres
|
||||
panel.sleep() nichts mehr zu tun haette."""
|
||||
self.asleep = True
|
||||
|
||||
|
||||
@@ -434,20 +452,31 @@ def run_clear(revision: str) -> None:
|
||||
log.info("Panel geleert (EPD_REVISION=%s)", revision)
|
||||
|
||||
|
||||
def run_daemon(revision: str) -> None:
|
||||
def run_daemon(revision: str, clock=time, panel: "Panel | None" = None,
|
||||
read_state=build_state) -> None:
|
||||
"""Endlosschleife: alle POLL_INTERVAL_S Sekunden den Zustand einlesen und die von
|
||||
RefreshPlanner entschiedene Aktion ausfuehren. Hier steht bewusst KEINE Zeitlogik mehr
|
||||
(Fixrunde 4) - die steckt vollstaendig in RefreshPlanner und ist dort ohne Schleife,
|
||||
ohne time.sleep() und ohne Hardware pruefbar."""
|
||||
panel = Panel(revision)
|
||||
ohne time.sleep() und ohne Hardware pruefbar.
|
||||
|
||||
`clock` (braucht monotonic() und sleep()), `panel` und `read_state` sind einschleusbar,
|
||||
damit die VERDRAHTUNG hier - welche Aktion welche Panel-Methode ruft, dass die
|
||||
Selbst-Aufwertung von draw_partial() zurueckgemeldet wird, dass das Einschlafen
|
||||
zurueckgemeldet wird, und die Reihenfolge decide -> ausfuehren -> zurueckmelden -
|
||||
selbst pruefbar ist und nicht nur eine Kopie davon (Fixrunde 5, N2: alle drei
|
||||
Mutationen an genau diesen Zeilen blieben vorher gruen, weil der Test eine nachgebaute
|
||||
Schleife statt dieser hier trieb). Im Betrieb bleiben die Vorgaben: echte Uhr, echtes
|
||||
Panel, echtes build_state()."""
|
||||
if panel is None:
|
||||
panel = Panel(revision)
|
||||
panel.clear()
|
||||
planner = RefreshPlanner(time.monotonic())
|
||||
planner = RefreshPlanner(clock.monotonic())
|
||||
|
||||
while True:
|
||||
state = build_state()
|
||||
state = read_state()
|
||||
current_lines = render.lines(state)
|
||||
mode = state.get("mode")
|
||||
now = time.monotonic()
|
||||
now = clock.monotonic()
|
||||
|
||||
action = planner.decide(now, current_lines, mode)
|
||||
if action == "full":
|
||||
@@ -460,9 +489,9 @@ def run_daemon(revision: str) -> None:
|
||||
planner.record_draw(now, current_lines, mode, was_full=upgraded)
|
||||
elif action == "sleep":
|
||||
panel.sleep()
|
||||
planner.record_sleep(now)
|
||||
planner.record_sleep()
|
||||
|
||||
time.sleep(POLL_INTERVAL_S)
|
||||
clock.sleep(POLL_INTERVAL_S)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
Reference in New Issue
Block a user