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.
98 lines
2.7 KiB
Python
98 lines
2.7 KiB
Python
from styles.blink import (
|
|
STATE_BLINKING,
|
|
STATE_EVALUATING,
|
|
STATE_WAIT_BEFORE_BLINK,
|
|
STATE_WAIT_BEFORE_RETRY,
|
|
STATE_WAIT_FOR_INPUT,
|
|
BlinkStyle,
|
|
)
|
|
|
|
|
|
def _led():
|
|
"""Return (calls list, set_led callable recording each call)."""
|
|
calls = []
|
|
return calls, lambda on: calls.append(on)
|
|
|
|
|
|
def test_press_starts_puzzle():
|
|
_, set_led = _led()
|
|
style = BlinkStyle(set_led)
|
|
assert style.update(100.0, True) is True
|
|
assert style.state == STATE_WAIT_BEFORE_BLINK
|
|
|
|
|
|
def test_blink_correct_sequence_stops(monkeypatch):
|
|
_, set_led = _led()
|
|
style = BlinkStyle(set_led)
|
|
|
|
style.update(100.0, True) # press -> start puzzle
|
|
assert style.state == STATE_WAIT_BEFORE_BLINK
|
|
|
|
monkeypatch.setattr("styles.blink.random.randint", lambda a, b: 3)
|
|
style.update(103.1, False) # 3s passed -> blinking, 3 blinks
|
|
assert style.state == STATE_BLINKING
|
|
assert style.target_blinks == 3
|
|
|
|
style.update(105.0, False) # run through the blink sequence
|
|
assert style.state == STATE_WAIT_FOR_INPUT
|
|
|
|
# Press exactly 3 times
|
|
for t in (105.5, 106.0, 106.5):
|
|
style.update(t, True)
|
|
style.update(t + 0.05, False)
|
|
assert style.user_presses == 3
|
|
|
|
style.update(109.7, False) # 3s idle -> evaluating
|
|
assert style.state == STATE_EVALUATING
|
|
assert style.update(109.8, False) is False # correct -> stop
|
|
|
|
|
|
def test_blink_incorrect_retries():
|
|
_, set_led = _led()
|
|
style = BlinkStyle(set_led)
|
|
style.state = STATE_WAIT_FOR_INPUT
|
|
style.target_blinks = 3
|
|
|
|
style.update(100.0, True) # one press
|
|
style.update(100.1, False)
|
|
|
|
style.last_interaction_time = 100.1
|
|
style.update(103.2, False) # -> evaluating
|
|
keep_running = style.update(103.3, False) # 1 != 3 -> retry
|
|
|
|
assert keep_running is True
|
|
assert style.state == STATE_WAIT_BEFORE_RETRY
|
|
|
|
|
|
def test_blinking_non_blocking():
|
|
calls, set_led = _led()
|
|
style = BlinkStyle(set_led)
|
|
style.state = STATE_BLINKING
|
|
style.target_blinks = 2
|
|
|
|
style.update(100.0, False) # start of blinking: LED on
|
|
assert style.state == STATE_BLINKING
|
|
assert calls[-1] is True
|
|
|
|
style.update(100.3, False) # mid-blink toggles by elapsed time
|
|
assert calls[-1] is False
|
|
|
|
# 2 blinks * 2 phases * 0.3s = 1.2s
|
|
style.update(101.2, False)
|
|
assert style.state == STATE_WAIT_FOR_INPUT
|
|
assert calls[-1] is False
|
|
|
|
|
|
def test_blinking_advances_time():
|
|
_, set_led = _led()
|
|
style = BlinkStyle(set_led)
|
|
style.state = STATE_BLINKING
|
|
style.target_blinks = 4
|
|
|
|
# 4 blinks * 2 phases * 0.3s = 2.4s
|
|
style.update(100.0, False)
|
|
style.update(102.4, False)
|
|
|
|
assert style.state == STATE_WAIT_FOR_INPUT
|
|
assert style.last_interaction_time == 102.4
|