The failsafe silences AUDIO ONLY at 300 s. The lock, the typed challenge and
the workout gate all persist, so waiting it out in bed is not an escape --
it only stops a ring nobody is present to dismiss from sounding forever.
Adding it exposed two collisions that would each have quietly defeated the
alarm:
1. The escalation ladder ended outside the audible window. PHASE_SOFT_END
was 5 minutes and PHASE_MEDIUM_END 15, so with the failsafe at 5 minutes
the alarm was silenced before it ever left its SOFTEST rung -- the medium
and loud phases became unreachable code. Printed timeline before the fix:
0s soft 440Hz ... 299s soft 440Hz | 300s failsafe-silent
Phases are now 1 and 2 minutes, so the alarm spends 3 of its 5 audible
minutes on the loud alternating rung, which is what makes lying in bed
for five minutes unattractive.
2. ESCAPE_OFFER_AFTER_SECONDS was also 300, so the escape hatch appeared in
the same instant the sound stopped -- at its most tempting, precisely when
the alarm had just stopped being annoying. Moved to 900.
Both invariants are now locked in by tests, so retuning one constant without
the other fails instead of silently degrading the alarm.
Also fixes a latent test bug this surfaced: TestBeepLoopPhases anchored
elapsed time to "one minute past the previous boundary", so a phase retune
made it miss its rung, never set _stop_beep, and spin forever calling the
REAL audio stack -- hanging the whole suite rather than failing. It now uses
the midpoint of the band and patches every rung, so a mistuned boundary fails
fast.
test_alarm_part3.py split (372 -> 195) for the 250-line gate.
413 tests, 100% branch coverage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
|
||
|---|---|---|
| .github/workflows | ||
| docs/explanations | ||
| phone_app | ||
| prompts | ||
| scripts | ||
| wake_alarm | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| DOCS-speaker-recommendation.md | ||
| install.sh | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| shutdown-wrapper.sh | ||
| sleep-hook.sh | ||
| TODO-file-length-250.md | ||
| wake-alarm-fans.sh | ||
| wake-alarm-trigger.service | ||
| wake-alarm-trigger.timer | ||
| wake-alarm.service | ||
wake_alarm
A phone-driven wake alarm for an always-on PC. The alarm time is whatever the
companion phone app last synced (via the shared crdt_sync transport over a
private GitHub repo); a per-minute systemd user timer fires at exactly that
time, showing a fullscreen, challenge-dismissed alarm with fan ramp and
optional TP-Link Tapo P110 smart-plug control.
Change the alarm on the phone at any hour and the PC follows within ~5
minutes. If the PC does happen to be powered down, a shutdown wrapper
hibernates with rtcwake set to the same synced time, as a fallback.
Install
bash install.sh
This installs the package + dependencies into system Python's user
site-packages (the systemd units run /usr/bin/python directly, not a venv —
see CLAUDE.md), installs and enables the per-minute trigger timer, the
systemd-sleep resume hook, the shutdown wrapper that hibernates with
rtcwake as a fallback, the fan-ramp script, and (optionally) python-kasa
for smart-plug control.
Reading the phone's alarm needs a GitHub token with repo scope, at
~/.config/wake_alarm/sync.json as {"token": "..."}. If that file is
absent, a sibling app's ~/.config/{diet_guard,screen_locker}/sync_token is
used instead, so one token covers every app syncing through kuhyx/syncs.
Usage
python -m wake_alarm._alarm --demo # test the alarm window (safe, closeable)
python -m wake_alarm._sync --show # what alarm time the PC resolves
python -m wake_alarm._trigger --dry-run # what the next tick would do
journalctl --user -fu wake-alarm-trigger.service # watch it live
The alarm fires automatically once installed; no manual invocation is needed in normal operation.
Development
python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/pre-commit install && .venv/bin/pre-commit install --hook-type pre-push
.venv/bin/python -m pytest wake_alarm/tests/ --cov=wake_alarm --cov-branch --cov-fail-under=100
See CLAUDE.md for scheduling details, the hibernate/rtcwake mechanism,
the cross-repo workout_log.json read, and production deployment gotchas.