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

14 KiB
Raw Blame History

phase, verified, status, score, re_verification, human_verification
phase verified status score re_verification human_verification
01-esp32-firmware 2026-04-03T12:00:00Z passed 5/5 must-haves verified false
test expected why_human
All 8 animations produce visually distinct output on physical hardware 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 Animation visual distinctiveness requires eyes-on hardware validation — cannot verify pixel patterns programmatically on VPS
test expected why_human
STATUS response accuracy 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 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
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)