Keep the LED on when a blink gives way to a steady level #7

Merged
balint merged 1 commit from fix/led-blink-cancel-clobber into main 2026-06-06 22:14:56 +01:00
Owner

On a real Pi, ending a call left the on-board LED dark even though the state was SOLID:

recv CANCEL (state=RINGING)
state: RINGING -> IDLE
led: SOLID          <- logged solid

…but the LED was physically off.

Why

set_state -> _apply(SOLID) cancels the running blink task and then writes the steady brightness (1) synchronously. But _cancel_blink() only schedules the cancellation — the blink coroutine's except asyncio.CancelledError: self._write_brightness(0) cleanup runs a loop turn later, landing after the SOLID 1 and clobbering it back to 0. Dev mode hid it because sysfs writes are no-ops there.

Change

  • Drop the cleanup _write_brightness(0) from _slow_blink/_double_blip. The steady level after a blink is always set by whoever cancels it — _apply's driver (OFF/SOLID/next blink) or stop() — so the cleanup write was redundant as well as racy.
  • Add a regression test driving DOUBLE_BLIP -> SOLID and asserting the final brightness write is 1. Verified it fails (assert 0 == 1) against the old ordering and passes with the fix.

🤖 Generated with Claude Code

On a real Pi, ending a call left the on-board LED dark even though the state was `SOLID`: ``` recv CANCEL (state=RINGING) state: RINGING -> IDLE led: SOLID <- logged solid ``` …but the LED was physically off. ## Why `set_state -> _apply(SOLID)` cancels the running blink task and then writes the steady brightness (`1`) synchronously. But `_cancel_blink()` only *schedules* the cancellation — the blink coroutine's `except asyncio.CancelledError: self._write_brightness(0)` cleanup runs a loop turn **later**, landing after the SOLID `1` and clobbering it back to `0`. Dev mode hid it because sysfs writes are no-ops there. ## Change - Drop the cleanup `_write_brightness(0)` from `_slow_blink`/`_double_blip`. The steady level after a blink is always set by whoever cancels it — `_apply`'s driver (OFF/SOLID/next blink) or `stop()` — so the cleanup write was redundant as well as racy. - Add a regression test driving `DOUBLE_BLIP -> SOLID` and asserting the final brightness write is `1`. Verified it fails (`assert 0 == 1`) against the old ordering and passes with the fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
On a real Pi, ending a call (RINGING -> IDLE) left the on-board LED dark even
though the state was SOLID. set_state -> _apply cancels the running blink task
and then writes the new steady brightness synchronously, but the blink
coroutine's CancelledError cleanup wrote brightness 0 a loop turn later —
landing after, and so clobbering, the SOLID 1. Dev mode hid it (sysfs writes
are no-ops).

Drop the blink coroutines' cleanup writes. The level after a blink is always
set by whoever cancels it — _apply's driver (OFF/SOLID/next blink) or stop() —
so the cleanup write was redundant as well as racy. Add a regression test that
drives DOUBLE_BLIP -> SOLID and asserts the final write is on; it fails against
the old ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
balint merged commit f96d047858 into main 2026-06-06 22:14:56 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
balint/phone!7
No description provided.