feat: add pluggable alarm styles and migrate wecker.py

Introduce a styles/ plugin package:
- styles/base.py: AlarmStyle ABC (injected set_led, update()->bool contract)
- styles/blink.py: BlinkStyle, the existing count-the-blinks puzzle moved
  out of wecker.py's AlarmClock
- styles/simple.py: SimpleStyle, press-once-to-stop (the new default style)
- styles/__init__.py: STYLES registry + get_style() validator + LEGACY_STYLE

wecker.py now resolves the style via the registry and owns only the shared
music/button/cleanup loop; the --style CLI arg defaults to blink so existing
cron entries keep their behaviour. Unknown styles raise before hardware init.
This commit is contained in:
2026-07-31 16:19:14 +02:00
parent 7ee28d72b6
commit b3f3db6f13
9 changed files with 395 additions and 212 deletions
+29
View File
@@ -0,0 +1,29 @@
from abc import ABC, abstractmethod
from typing import Callable
class AlarmStyle(ABC):
"""A pluggable alarm behaviour.
A style decides *when* the alarm stops and what the LED does while it
rings. It never touches hardware directly: ``set_led`` is injected by the
runner, so styles stay import-safe in the API process (no GPIO/pygame).
Lifecycle: construct once with ``set_led``, then call ``update`` every
tick. Return ``False`` from ``update`` to stop the alarm.
"""
def __init__(self, set_led: Callable[[bool], None]):
self.set_led = set_led
@abstractmethod
def update(self, now: float, is_pressed: bool) -> bool:
"""Advance the style by one tick.
Args:
now: current time (``time.time()`` from the runner).
is_pressed: whether the button is currently held down.
Returns:
True to keep ringing, False to stop.
"""