diff --git a/.planning/phases/07-microcontroller-firmware/07-03-SUMMARY.md b/.planning/phases/07-microcontroller-firmware/07-03-SUMMARY.md new file mode 100644 index 0000000..6adfd24 --- /dev/null +++ b/.planning/phases/07-microcontroller-firmware/07-03-SUMMARY.md @@ -0,0 +1,72 @@ +--- +phase: "07" +plan: "03" +subsystem: firmware +tags: [firmware, micropython, frame-mode, drgb, drgbw, dnrgb, udp] +dependency_graph: + requires: [07-01, 07-02] + provides: [firmware/protocol.py#FrameAssembler, firmware/main.py#frame-mode] + affects: [lightsync/protocol/drgb.py] +tech_stack: + added: [] + patterns: [DNRGB multi-packet reassembly, frame timeout with animation fallback] +key_files: + created: [tests/test_firmware_frames.py] + modified: [firmware/protocol.py, firmware/main.py] +decisions: + - "Frame timeout (2s) reverts to last animation, not idle — matches WLED behavior" + - "frame_is_rgbw flag routes DRGB/DNRGB to write_rgb, DRGBW to write_rgbw" + - "FrameAssembler keyed by absolute pixel index (start_index + i) — tolerates out-of-order chunks" +metrics: + duration: "2 minutes" + completed: "2026-04-07" + tasks_completed: 3 + files_changed: 3 +--- + +# Phase 7 Plan 3: Frame Mode Renderer Summary + +**One-liner:** DRGB/DRGBW/DNRGB raw frame handling with FrameAssembler multi-packet reassembly and 2s animation fallback timeout. + +## What Was Built + +Added raw frame mode to the firmware. The device now receives pixel data from the server and writes it directly to the LED strip, supporting three packet types: + +- **DRGB** (0x02): 3 bytes/pixel RGB, up to 490 pixels per packet — routed to `strip.write_rgb()` +- **DRGBW** (0x03): 4 bytes/pixel RGBW, up to 367 pixels per packet — routed to `strip.write_rgbw()` +- **DNRGB** (0x04): chunked RGB with start_index for large strips — reassembled by `FrameAssembler`, then routed to `strip.write_rgb()` + +Frame mode takes priority over animation mode. When no frame packet arrives for 2 seconds, the firmware reverts to the last animation command. + +## Tasks Completed + +| Task | Description | Commit | Files | +|------|-------------|--------|-------| +| 1 | Add FrameAssembler to firmware/protocol.py | d14fa3c | firmware/protocol.py | +| 2 | Replace firmware/main.py with frame mode version | 2a22eef | firmware/main.py | +| 3 | Create tests/test_firmware_frames.py (19 tests) | ec97600 | tests/test_firmware_frames.py | + +## Verification + +- [x] `uv run pytest tests/test_firmware_frames.py -v` — 19 passed in 0.11s +- [x] `firmware/protocol.py` contains `FrameAssembler` with `feed()`, `reset()`, 200ms timeout +- [x] `firmware/main.py` handles DRGB (write_rgb), DRGBW (write_rgbw), DNRGB (via FrameAssembler) +- [x] Frame timeout (2s) reverts to last animation command mode +- [x] DNRGB 600-LED multi-packet frame: 2 packets (489 + 111 LEDs) round-trip passes +- [x] 300-LED DRGB = 902 bytes, DRGBW = 1202 bytes — both fit in single UDP packet +- [x] FrameAssembler clears stale partial frames after 200ms timeout + +## Deviations from Plan + +None — plan executed exactly as written. + +## Known Stubs + +None — all frame handling is fully wired. + +## Self-Check: PASSED + +- firmware/protocol.py exists with FrameAssembler: FOUND +- firmware/main.py exists with frame mode: FOUND +- tests/test_firmware_frames.py exists: FOUND +- Commits d14fa3c, 2a22eef, ec97600: FOUND