from abc import ABC, abstractmethod from typing import Callable class AlarmStyle(ABC): """A pluggable alarm behaviour. A style owns *its own ringing tone*: it uses the audio engine (pygame) directly and decides when the alarm stops and what the LED does. It never touches hardware directly: ``set_led`` is injected by the runner, and the runner owns the audio engine lifecycle (mixer init/quit). Styles stay import-safe in the API process — ``import pygame`` does not initialise audio, so the API can import styles just to validate names. Lifecycle: construct once with ``set_led``/``music_file``; call ``start()`` to begin ringing (kick off the tone and initial LED); then call ``update`` every tick. Return ``False`` from ``update`` to stop the alarm. """ def __init__( self, set_led: Callable[[bool], None], music_file: str | None = None ): self.set_led = set_led self.music_file = music_file def start(self) -> None: """Begin ringing. Override to start the style's tone / initial 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. """