Go to file
Krzysztof kuhy Rudnicki 08b44b4ca2 Fix three defects the hardware run exposed in the room-proof path
All three were invisible to the emulator (no NFC, no step sensor) and to the
widget tests (which mock the platform channel), and each one on its own made
the alarm harder or impossible to dismiss.

1. AlarmActivity.onPause called NfcReader.stop(). enableReaderMode is
   per-activity but disableReaderMode is effectively global, so anything
   briefly coming in front of the ring screen killed NFC polling for the rest
   of the ring. Measured on the Pixel: `flags: 271` immediately followed by
   `flags: 0`. Teardown moves to onDestroy, and onResume re-asserts.

2. StepCounter could never report progress from a standing start.
   TYPE_STEP_COUNTER is an on-change *cumulative* sensor: it emits nothing
   until the user has already walked, and the first emission was being
   consumed as the baseline — so the ring screen sat at 0/25 no matter how far
   you walked. TYPE_STEP_DETECTOR is now primary (one event per stride, no
   baseline to miss); the counter stays as the fallback for devices without a
   detector.

3. NFC cannot be read while the ring shows over the lockscreen. Measured
   A/B on the Pixel with the same tag: registration while unlocked →
   mScreenState=ON_UNLOCKED, tag discovered; ring screen over the keyguard →
   mScreenState=ON_LOCKED, reader mode accepted (flags 271) but no discovery.
   Android gates NFC polling on lock state. showWhenLocked draws *over* the
   keyguard without unlocking, so the alarm now calls
   requestDismissKeyguard() on fire and re-asserts reader mode from the
   dismiss callback. User's call, taken explicitly: on a secured phone this
   surfaces the PIN prompt, which is an acceptable trade for an alarm you
   must get up for.

Without (3) the only satisfiable proof at 06:00 is steps, which is exactly
the single-mechanism dependency the room-proof redundancy exists to prevent.

flutter test: 69 pass. dart analyze lib/ test/: clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
2026-08-27 22:23:23 +02:00
.github/workflows Adopt the four markdown namespaces and gate them 2026-08-24 15:53:51 +02:00
docs/explanations Add change explanation: phone-driven-alarm-timer 2026-07-20 09:17:12 +02:00
phone_app Fix three defects the hardware run exposed in the room-proof path 2026-08-27 22:23:23 +02:00
prompts Add the removal marker to every TODO file 2026-08-24 15:56:24 +02:00
scripts Phase 6: vmbox becomes the gate for the system scripts (item 10) 2026-08-27 20:49:16 +02:00
wake_alarm Phase 7: curtains open on the alarm, no-op until hardware exists (item 5) 2026-08-27 21:00:11 +02:00
.gitignore Add CI-mirror pre-push gate + requirements-sync check 2026-07-11 17:51:16 +02:00
.pre-commit-config.yaml Phase 0: fix dependency pin mismatch, stale docs, add speaker research 2026-08-26 22:53:01 +02:00
AGENTS.md Bring the two files this change grew back under the 250-line gate 2026-08-27 21:43:16 +02:00
CLAUDE.md Phase 0: fix dependency pin mismatch, stale docs, add speaker research 2026-08-26 22:53:01 +02:00
DOCS-architecture.md Add the continuation prompt for the remaining hardening phases 2026-08-27 20:05:14 +02:00
DOCS-curtains-hardware.md Phase 7: curtains open on the alarm, no-op until hardware exists (item 5) 2026-08-27 21:00:11 +02:00
DOCS-phone-alarm.md Bring the two files this change grew back under the 250-line gate 2026-08-27 21:43:16 +02:00
DOCS-speaker-recommendation.md Phase 0: fix dependency pin mismatch, stale docs, add speaker research 2026-08-26 22:53:01 +02:00
DOCS-vmbox.md Phase 6: vmbox becomes the gate for the system scripts (item 10) 2026-08-27 20:49:16 +02:00
install.sh Phase 6: vmbox becomes the gate for the system scripts (item 10) 2026-08-27 20:49:16 +02:00
pyproject.toml Bump gatelock to v0.4.1: keep the dismiss box focused after a monitor blip 2026-08-12 22:17:31 +02:00
README.md Phase 6: vmbox becomes the gate for the system scripts (item 10) 2026-08-27 20:49:16 +02:00
requirements.txt Bump gatelock to v0.4.1: keep the dismiss box focused after a monitor blip 2026-08-12 22:17:31 +02:00
shutdown-wrapper.sh Read the phone's alarm from Firebase, keeping GitHub as the mirror 2026-08-05 09:09:22 +02:00
sleep-hook.sh Extract wake_alarm from testsAndMisc as a standalone repo 2026-06-22 12:31:40 +02:00
TODO-file-length-250.md Add the removal marker to every TODO file 2026-08-24 15:56:24 +02:00
TODO-hardening-continue.md Record NFC registerable, and the two things still unverified on hardware 2026-08-27 21:39:22 +02:00
wake-alarm-trigger.service Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
wake-alarm-trigger.timer Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
wake-alarm.service Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00

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.