Files
led-sync-studio/.planning/phases/01-esp32-firmware/01-VERIFICATION.md
2026-04-03 13:42:00 +02:00

132 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 01-esp32-firmware
verified: 2026-04-03T12:00:00Z
status: passed
score: 5/5 must-haves verified
re_verification: false
human_verification:
- test: "All 8 animations produce visually distinct output on physical hardware"
expected: "Chase shows moving window, Rainbow shows HSV sweep, Strobe flickers, Sparkle shows random flashes, Pulse/Breathe show sine envelopes, ColorWash fades between colors, GradientSweep sweeps two-color gradient"
why_human: "Animation visual distinctiveness requires eyes-on hardware validation — cannot verify pixel patterns programmatically on VPS"
- test: "STATUS response accuracy"
expected: "buildStatusResponse() in wifi_server.cpp uses hardcoded 'unknown'/'unknown'/40 — not wired to live ZoneState. Verify whether accurate status is needed in Phase 2 before wiring"
why_human: "Known incomplete stub; not a Phase 1 requirement but worth human decision on whether to fix before Phase 2 begins"
---
# Phase 1: ESP32 Firmware Verification Report
**Phase Goal:** Both LED strips are driven by one ESP32-C3 that receives JSON commands over WiFi and executes named animations autonomously
**Verified:** 2026-04-03T12:00:00Z
**Status:** PASSED
**Re-verification:** No — initial verification
## Goal Achievement
### Observable Truths (from ROADMAP.md Success Criteria)
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | WS2801 (160 LEDs, SPI) and SK6812 (300 LEDs, RMT/RGBW) both illuminate correctly and simultaneously with no flicker from WiFi interference | VERIFIED | `led_driver.cpp`: `stripWand.Show()` (RMT DMA) + `stripSchrank.Show()` (SPI 2MHz) in `showBothStrips()`; `esp_wifi_set_ps(WIFI_PS_NONE)` in `main.cpp` L36; hardware checkpoint in 01-01-SUMMARY.md: "Zero visible flicker during 300-packet UDP burst test" — APPROVED |
| 2 | Sending a UDP JSON command triggers named animation on correct strip within 50ms | VERIFIED | `wifi_server.cpp`: `AsyncUDP.onPacket` -> `xQueueSendFromISR` -> `parseTask` -> `commandQueue`; `animation_engine.cpp`: queue drained at start of each 50fps frame (20ms worst case + parse overhead); hardware checkpoint in 01-04-SUMMARY.md: "UDP JSON chase command triggers Wand strip within 50ms — PASS" |
| 3 | All 8 built-in animations run on either zone without crashing | VERIFIED | `ANIM_REGISTRY[8]` in `animation_engine.cpp`: chase, pulse, rainbow, strobe, color_wash, breathe, sparkle, gradient_sweep — all 8 `.cpp` files exist in `firmware/src/animations/`; 01-04 hardware checkpoint: "All 8 animations run visually distinct on both zones — PASS"; "10-minute continuous operation with no crash or watchdog reset — PASS" |
| 4 | Each zone can be commanded independently — different animations run on Schrank and Wand simultaneously | VERIFIED | `applyCommand()` in `animation_engine.cpp`: routes by `cmd.zone` (SCHRANK / WAND / ALL) to independent `ZoneState` structs; `parseCommand()` in `protocol.cpp`: zones parsed at L33-37; hardware checkpoint: "Schrank and Wand run different animations simultaneously — PASS" |
| 5 | Animation parameters (color/RGBW, speed, intensity) visibly change behavior when included in JSON command | VERIFIED | `AnimParams` struct in `protocol.h`: speed/intensity/colors/direction; all animations apply `params.intensity * g_brightness` as brightness factor and use `params.speed` to scale timing; `applyBrightnessW/R()` in `led_driver.cpp` enforces brightness cap; hardware checkpoint: "Speed, color, intensity parameters visibly change behavior — PASS" |
**Score:** 5/5 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `firmware/platformio.ini` | PlatformIO build config with `-DESP32_ARDUINO_NO_RGB_BUILTIN` | VERIFIED | Contains flag, NeoPixelBus ^2.8.0, ArduinoJson ^7.4.0 — exact match |
| `firmware/src/config.h` | Pin definitions, brightness cap, LED count constants | VERIFIED | All 11 constants present: PIN_SK6812=3, PIN_WS2801_MOSI=7, PIN_WS2801_CLK=6, LED_COUNT_SCHRANK=160, LED_COUNT_WAND=300, BRIGHTNESS_CAP=0.40f, BRIGHTNESS_MAX=0.60f, UDP_PORT=4210, ANIM_FPS=50, FreeRTOS priorities and stacks |
| `firmware/src/led_driver.h` | Strip type aliases + showBothStrips() API | VERIFIED | `StripWand = NeoPixelBus<NeoGrbwFeature, NeoEsp32Rmt0Ws2812xMethod>`, `StripSchrank = NeoPixelBus<NeoRgbFeature, NeoWs2801Spi2MhzMethod>`, all 6 function declarations present |
| `firmware/src/led_driver.cpp` | Concurrent show(), brightness clamping | VERIFIED | `stripWand.Show()` before `stripSchrank.Show()`, `applyBrightnessW/R` clamp to `[0.0f, BRIGHTNESS_MAX]`, `initStrips()` logs to Serial |
| `firmware/src/main.cpp` | Setup launching all FreeRTOS tasks in correct order | VERIFIED | `initStrips()``initAnimationEngine()` → WiFi → `esp_wifi_set_ps(WIFI_PS_NONE)``startUdpServer()`; WiFi reconnect monitor in `loop()` |
| `firmware/src/protocol.h` | LedCommand struct, CmdType, Zone, parseCommand() | VERIFIED | All types present: CmdType (ANIMATION/STOP/BRIGHTNESS/STATUS/UNKNOWN), Zone (SCHRANK/WAND/ALL/UNKNOWN), LedCommand with all fields, parseCommand() and buildStatusResponse() declarations |
| `firmware/src/protocol.cpp` | Full JSON deserialization with zone routing | VERIFIED | ArduinoJson v7 `deserializeJson`, version check (v:1), zone routing for all 3 zones, animation/stop/brightness/status command dispatch, 148 lines |
| `firmware/src/wifi_server.h` | AsyncUDP server, commandQueue extern | VERIFIED | `extern QueueHandle_t commandQueue`, `startUdpServer()` declaration |
| `firmware/src/wifi_server.cpp` | Two-queue FreeRTOS pattern (rawQueue → parseTask → commandQueue) | VERIFIED | `AsyncUDP.onPacket` uses `xQueueSendFromISR(rawQueue)`, separate `parseTask` uses `xQueueReceive(rawQueue, &raw, portMAX_DELAY)`, JSON never parsed in callback |
| `firmware/src/animation_engine.h` | ZoneState struct, g_brightness, initAnimationEngine() | VERIFIED | ZoneState with currentAnim/params/tick/active/animName, extern zoneSchrank/zoneWand/g_brightness, autoWhite() declaration |
| `firmware/src/animation_engine.cpp` | ANIM_REGISTRY[8], 50fps vTaskDelayUntil, command dispatch | VERIFIED | All 8 entries in ANIM_REGISTRY, `vTaskDelayUntil` with `pdMS_TO_TICKS(1000/ANIM_FPS)`, non-blocking `xQueueReceive(..., 0)` drain, `showBothStrips()` after each frame |
| `firmware/src/animations/animation_base.h` | AnimTickFn typedef, AnimEntry struct, ANIM_REGISTRY extern | VERIFIED | `typedef void (*AnimTickFn)(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW)`, all 8 function forward-declarations, ANIM_REGISTRY extern |
| `firmware/src/animations/chase.cpp` | Moving window animation | VERIFIED | 54 lines, speed scales position advance, window wraps, isRGBW branch, autoWhite() for W channel |
| `firmware/src/animations/pulse.cpp` | Sine wave brightness | VERIFIED | 29 lines, sine envelope |
| `firmware/src/animations/rainbow.cpp` | HSV rainbow | VERIFIED | 32 lines, HsbColor HSV generation, ignores palette |
| `firmware/src/animations/strobe.cpp` | On/off flicker | VERIFIED | 37 lines, period = 20/speed ticks |
| `firmware/src/animations/color_wash.cpp` | Linear palette fade | VERIFIED | 45 lines, hold_ticks = 50/speed |
| `firmware/src/animations/breathe.cpp` | Sine envelope with 5% minimum | VERIFIED | 33 lines, 5% minimum brightness per D-11 |
| `firmware/src/animations/sparkle.cpp` | Random pixel flash | VERIFIED | 50 lines, `esp_random()` hardware RNG |
| `firmware/src/animations/gradient_sweep.cpp` | Two-color gradient | VERIFIED | 44 lines, two-color blend |
| `docs/protocol.md` | Phase 2 protocol reference | VERIFIED | UDP port 4210, all 8 animation names, full command schema |
| `firmware/.gitignore` | Excludes .pio/ and credentials.h | VERIFIED | Both entries present |
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `main.cpp` | `led_driver.cpp` | `initStrips()` in setup() | WIRED | L15 in main.cpp |
| `main.cpp` | `animation_engine.cpp` | `initAnimationEngine()` in setup() | WIRED | L19 in main.cpp |
| `main.cpp` | `wifi_server.cpp` | `startUdpServer()` after WIFI_PS_NONE | WIRED | L41 in main.cpp |
| `wifi_server.cpp` | `protocol.cpp` | `rawQueue` — callback copies bytes, parseTask deserializes | WIRED | `xQueueSendFromISR(rawQueue)` in callback; `xQueueReceive(rawQueue)` in parseTask |
| `wifi_server.cpp` | `animation_engine.cpp` | `commandQueue` — parsed LedCommand to animation engine | WIRED | `xQueueSend(commandQueue, &cmd)` in parseTask; `xQueueReceive(commandQueue, &cmd, 0)` in animTickTask |
| `animation_engine.cpp` | `led_driver.cpp` | `showBothStrips()` after each 50fps frame | WIRED | L138 in animation_engine.cpp |
| `animation_engine.cpp` | `wifi_server.h` | `commandQueue` extern | WIRED | `#include "wifi_server.h"` in animation_engine.cpp |
| `animation_base.h` | all 8 animations | `AnimTickFn` function pointer via ANIM_REGISTRY | WIRED | ANIM_REGISTRY[8] maps name strings to all 8 function pointers |
### Data-Flow Trace (Level 4)
This phase produces no dynamic data rendering (no UI components). All data flows are hardware signal writes — LED strip output is verified by hardware acceptance test, not programmatic data-flow tracing. Level 4 is not applicable for embedded C++ firmware.
### Behavioral Spot-Checks
Step 7b: SKIPPED — No runnable entry points on VPS. Firmware targets ESP32-C3 hardware; `pio run` is not available in this environment. Hardware acceptance tests documented in plan summaries serve as the behavioral validation record.
### Requirements Coverage
| Requirement | Source Plan(s) | Description | Status | Evidence |
|-------------|---------------|-------------|--------|----------|
| FW-01 | 01-01, 01-04 | ESP32-C3 drives WS2801 (160, SPI) and SK6812 (300, RMT/RGBW) simultaneously | SATISFIED | `led_driver.cpp`: both strips initialized and driven in `showBothStrips()`; hardware PASS in 01-01-SUMMARY.md |
| FW-02 | 01-02, 01-04 | ESP32-C3 receives JSON commands over UDP and executes named animations | SATISFIED | `wifi_server.cpp` + `protocol.cpp`: AsyncUDP listener on port 4210, JSON parsed into LedCommand, dispatched via commandQueue; hardware PASS in 01-04-SUMMARY.md |
| FW-03 | 01-03, 01-04 | At least 8 built-in animations (Chase, Pulse, Rainbow, Strobe, Color Wash, Breathe, Sparkle, Gradient Sweep) | SATISFIED | ANIM_REGISTRY[8] in `animation_engine.cpp`, all 8 `.cpp` files exist and are substantive; hardware PASS |
| FW-04 | 01-03, 01-04 | Each animation accepts configurable parameters (color/RGBW, speed, intensity) | SATISFIED | `AnimParams` struct with speed/intensity/colors[8][4]/direction; all animations apply params; hardware PASS |
| FW-05 | 01-02, 01-04 | Both LED zones (Schrank/Wand) are independently controllable via JSON protocol | SATISFIED | `applyCommand()` routes Zone::SCHRANK/WAND/ALL to independent ZoneState structs; hardware PASS |
All 5 phase requirements (FW-01 through FW-05) are satisfied. No orphaned requirements — REQUIREMENTS.md maps FW-01 through FW-05 exclusively to Phase 1 and all are claimed by plans in this phase.
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `firmware/src/wifi_server.cpp` | 49 | `buildStatusResponse("unknown", "unknown", 40)` — hardcoded animation names and brightness in STATUS response | Warning | STATUS command returns incorrect current state. Documented as known stub in 01-02-SUMMARY.md. Not a Phase 1 requirement; STATUS accuracy is a quality issue for Phase 2 to address when Pi-side needs live state. No animations are blocked. |
**Classification:** The STATUS stub is a Warning (incomplete), not a Blocker. FW-01 through FW-05 do not require accurate STATUS responses. The stub does not prevent any animation from running or any command from being processed.
No TODO/FIXME/placeholder comments found in firmware source. No empty return patterns in animation or core files. All animation implementations are substantive (2954 lines each with real per-pixel logic).
### Human Verification Required
#### 1. Visual Animation Distinctiveness
**Test:** Flash firmware to ESP32-C3 SuperMini, send each of the 8 animation names via UDP to both `zone:wand` and `zone:schrank`
**Expected:** Each animation produces a visually distinct pattern — chase shows a moving window, rainbow shows HSV color sweep, strobe flickers on/off, sparkle shows random flashes, pulse/breathe show sine wave envelopes at different rates, color_wash fades between palette colors, gradient_sweep shows a two-color gradient moving along the strip
**Why human:** Per-pixel visual output cannot be verified programmatically without connected hardware; code review confirms the logic is correct but a brief visual check confirms no silent rendering errors
#### 2. STATUS Response Wiring Decision
**Test:** Send `{"v":1,"cmd":"status"}` while an animation is running
**Expected (current):** Response returns `{"v":1,"status":"ok","wand":"unknown","schrank":"unknown","brightness":40}` — hardcoded stub values
**Expected (fixed):** Response would return actual current animation names and brightness level
**Why human:** Determine whether accurate STATUS response is needed before Phase 2 begins, or whether Phase 2 plan should include wiring `zoneWand.animName`, `zoneSchrank.animName`, and `g_brightness` into `buildStatusResponse()`. This requires a product decision, not a code fix.
### Gaps Summary
No gaps blocking the phase goal. All 5 observable truths verified. All 22 required artifacts exist, are substantive, and are correctly wired into the build. All 5 firmware requirements (FW-01 through FW-05) are satisfied with hardware acceptance test evidence.
One known stub exists (`buildStatusResponse` hardcodes "unknown"/"unknown"/40) but it was documented explicitly in 01-02-SUMMARY.md, is not required by any FW-0x requirement, and does not prevent the phase goal from being achieved. It is flagged as a Warning for Phase 2 planning.
---
_Verified: 2026-04-03T12:00:00Z_
_Verifier: Claude (gsd-verifier)_