Dart side of the alarm (items 1, 4, 3). main.dart had no init seam at all; it now has ensureInitialized(), a navigatorKey and a /ring route. - challenge.dart: all five kinds ported from _challenges.py so both halves behave identically. kDismissCodeLength/kDismissFlashSeconds are duplicated from _constants.py, not synced — noted in the file, since Dart cannot read Python. - alarm_scheduler.dart: the Dart side of the single MethodChannel. - ring_screen.dart: room proof first, then a challenge, then dismiss. Back is a no-op (PopScope) so the alarm cannot be escaped. - room_proof.dart: NFC or QR satisfies on its own; steps NEVER do alone — the phone is stationary while you walk to it, so steps only accrue after pickup and can be faked by shaking it in bed. Steps count only together with elapsed time, as the fallback when both others fail. - home_screen: arms the app's own alarm instead of asking the user to confirm in the Clock app (item 1's "no user interaction"). SET_ALARM stays as redundancy with SKIP_UI=true. Re-arm has three hooks and needs all three: startup, BOOT_COMPLETED, and dismiss. The plan assumed LogStore.changes would cover this; that stream does not exist in crdt_sync_dart (verified — the library has no Stream at all), so the picker's save is only one hook and a restart would otherwise arm nothing. Fixed a real gap the API 36 AVD exposed: POST_NOTIFICATIONS was never requested at runtime, so a fresh install sat at importance=NONE and the full-screen intent was rejected outright — the alarm would ring with no dismiss screen. MainActivity now requests it, and canUseFullScreenIntent() also checks areNotificationsEnabled() so the check gates rather than warns. Verified on the API 36 AVD: screen Asleep -> Awake, AlarmActivity became the top resumed activity over the lockscreen, service isForeground=true types=0x2 (mediaPlayback) category=alarm, no crash. flutter test: 54 pass. dart analyze: clean. 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-architecture.md | ||
| DOCS-curtains-hardware.md | ||
| DOCS-speaker-recommendation.md | ||
| DOCS-vmbox.md | ||
| install.sh | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| shutdown-wrapper.sh | ||
| sleep-hook.sh | ||
| TODO-file-length-250.md | ||
| TODO-hardening-continue.md | ||
| TODO-phone-alarm-session.md | ||
| 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 escalating audio
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, 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.