docs(01-03): complete animation engine plan — 8 animations, 50fps FreeRTOS task, RGBW support
- SUMMARY documents ANIM_REGISTRY name strings for Pi-side use - W auto-derive formula documented for Phase 2 reference - FW-03 (8 animations) and FW-04 (configurable params) requirements marked complete - STATE updated: plan 4/4, progress 75%, decisions added
This commit is contained in:
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
- [x] **FW-01**: ESP32-C3 treibt WS2801 (160 LEDs, SPI) und SK6812 (300 LEDs, RMT/RGBW) gleichzeitig an
|
- [x] **FW-01**: ESP32-C3 treibt WS2801 (160 LEDs, SPI) und SK6812 (300 LEDs, RMT/RGBW) gleichzeitig an
|
||||||
- [x] **FW-02**: ESP32-C3 empfaengt JSON-Kommandos ueber UDP und fuehrt benannte Animationen aus
|
- [x] **FW-02**: ESP32-C3 empfaengt JSON-Kommandos ueber UDP und fuehrt benannte Animationen aus
|
||||||
- [ ] **FW-03**: Mindestens 8 Built-in-Animationen (Chase, Pulse, Rainbow, Strobe, Color Wash, Breathe, Sparkle, Gradient Sweep)
|
- [x] **FW-03**: Mindestens 8 Built-in-Animationen (Chase, Pulse, Rainbow, Strobe, Color Wash, Breathe, Sparkle, Gradient Sweep)
|
||||||
- [ ] **FW-04**: Jede Animation akzeptiert konfigurierbare Parameter (Farbe/RGBW, Speed, Intensitaet)
|
- [x] **FW-04**: Jede Animation akzeptiert konfigurierbare Parameter (Farbe/RGBW, Speed, Intensitaet)
|
||||||
- [x] **FW-05**: Beide LED-Zonen (Schrank/Wand) sind unabhaengig steuerbar ueber das JSON-Protokoll
|
- [x] **FW-05**: Beide LED-Zonen (Schrank/Wand) sind unabhaengig steuerbar ueber das JSON-Protokoll
|
||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
|-------------|-------|--------|
|
|-------------|-------|--------|
|
||||||
| FW-01 | Phase 1 | Complete |
|
| FW-01 | Phase 1 | Complete |
|
||||||
| FW-02 | Phase 1 | Complete |
|
| FW-02 | Phase 1 | Complete |
|
||||||
| FW-03 | Phase 1 | Pending |
|
| FW-03 | Phase 1 | Complete |
|
||||||
| FW-04 | Phase 1 | Pending |
|
| FW-04 | Phase 1 | Complete |
|
||||||
| FW-05 | Phase 1 | Complete |
|
| FW-05 | Phase 1 | Complete |
|
||||||
| AUD-01 | Phase 2 | Pending |
|
| AUD-01 | Phase 2 | Pending |
|
||||||
| AUD-02 | Phase 2 | Pending |
|
| AUD-02 | Phase 2 | Pending |
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Decimal phases appear between their surrounding integers in numeric order.
|
|||||||
Plans:
|
Plans:
|
||||||
- [x] 01-01-PLAN.md — PlatformIO scaffold, dual LED driver, WiFi+RMT coexistence validation
|
- [x] 01-01-PLAN.md — PlatformIO scaffold, dual LED driver, WiFi+RMT coexistence validation
|
||||||
- [x] 01-02-PLAN.md — UDP server, JSON protocol parser, FreeRTOS command queue
|
- [x] 01-02-PLAN.md — UDP server, JSON protocol parser, FreeRTOS command queue
|
||||||
- [ ] 01-03-PLAN.md — Animation engine (50fps FreeRTOS), all 8 built-in animations
|
- [x] 01-03-PLAN.md — Animation engine (50fps FreeRTOS), all 8 built-in animations
|
||||||
- [ ] 01-04-PLAN.md — System integration, final wiring, hardware acceptance checkpoint
|
- [ ] 01-04-PLAN.md — System integration, final wiring, hardware acceptance checkpoint
|
||||||
|
|
||||||
### Phase 2: App Core + Audio
|
### Phase 2: App Core + Audio
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: verifying
|
||||||
stopped_at: Completed 01-02-PLAN.md (2/2 tasks) — protocol.h/.cpp and wifi_server.h/.cpp implemented
|
stopped_at: Completed 01-03-PLAN.md (2/2 tasks) — animation engine and 8 animations implemented
|
||||||
last_updated: "2026-04-03T11:27:16.228Z"
|
last_updated: "2026-04-03T11:30:12.968Z"
|
||||||
last_activity: 2026-04-03
|
last_activity: 2026-04-03
|
||||||
progress:
|
progress:
|
||||||
total_phases: 4
|
total_phases: 4
|
||||||
completed_phases: 0
|
completed_phases: 0
|
||||||
total_plans: 4
|
total_plans: 4
|
||||||
completed_plans: 2
|
completed_plans: 3
|
||||||
percent: 0
|
percent: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ See: .planning/PROJECT.md (updated 2026-04-03)
|
|||||||
|
|
||||||
Phase: 01 (esp32-firmware) — EXECUTING
|
Phase: 01 (esp32-firmware) — EXECUTING
|
||||||
Plan: 4 of 4
|
Plan: 4 of 4
|
||||||
Status: Ready to execute
|
Status: Phase complete — ready for verification
|
||||||
Last activity: 2026-04-03
|
Last activity: 2026-04-03
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
Progress: [░░░░░░░░░░] 0%
|
||||||
@@ -55,6 +55,7 @@ Progress: [░░░░░░░░░░] 0%
|
|||||||
| Phase 01-esp32-firmware P01 | 3 | 3 tasks | 6 files |
|
| Phase 01-esp32-firmware P01 | 3 | 3 tasks | 6 files |
|
||||||
| Phase 01-esp32-firmware P01 | 21min | 4 tasks | 7 files |
|
| Phase 01-esp32-firmware P01 | 21min | 4 tasks | 7 files |
|
||||||
| Phase 01-esp32-firmware P02 | 3min | 2 tasks | 5 files |
|
| Phase 01-esp32-firmware P02 | 3min | 2 tasks | 5 files |
|
||||||
|
| Phase 01-esp32-firmware P03 | 14 | 2 tasks | 11 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -74,6 +75,8 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 01-esp32-firmware]: Two-queue FreeRTOS pattern: xQueueSendFromISR in AsyncUDP callback, parseTask drains rawQueue and dispatches LedCommand to commandQueue — prevents watchdog resets from JSON heap alloc in callback
|
- [Phase 01-esp32-firmware]: Two-queue FreeRTOS pattern: xQueueSendFromISR in AsyncUDP callback, parseTask drains rawQueue and dispatches LedCommand to commandQueue — prevents watchdog resets from JSON heap alloc in callback
|
||||||
- [Phase 01-esp32-firmware]: STATUS cmd handled inline in parseTask (no animation engine route) — needs sender IP/port from raw packet context, responds via udp.writeTo()
|
- [Phase 01-esp32-firmware]: STATUS cmd handled inline in parseTask (no animation engine route) — needs sender IP/port from raw packet context, responds via udp.writeTo()
|
||||||
- [Phase 01-esp32-firmware]: parseCommand() enforces brightness cap (max 60%) per D-09 — firmware-side safety cap regardless of Pi value
|
- [Phase 01-esp32-firmware]: parseCommand() enforces brightness cap (max 60%) per D-09 — firmware-side safety cap regardless of Pi value
|
||||||
|
- [Phase 01-esp32-firmware]: vTaskDelayUntil for 50fps drift-free animation tick — never drifts by compute time unlike vTaskDelay
|
||||||
|
- [Phase 01-esp32-firmware]: autoWhite formula: min(r,g,b)/2 warm-white approximation — triggers only when W=0 on RGBW strips
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -88,6 +91,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-03T11:27:16.219Z
|
Last session: 2026-04-03T11:30:12.955Z
|
||||||
Stopped at: Completed 01-02-PLAN.md (2/2 tasks) — protocol.h/.cpp and wifi_server.h/.cpp implemented
|
Stopped at: Completed 01-03-PLAN.md (2/2 tasks) — animation engine and 8 animations implemented
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
186
.planning/phases/01-esp32-firmware/01-03-SUMMARY.md
Normal file
186
.planning/phases/01-esp32-firmware/01-03-SUMMARY.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
phase: 01-esp32-firmware
|
||||||
|
plan: 03
|
||||||
|
subsystem: firmware
|
||||||
|
tags: [esp32, animation-engine, freertos, neopixelbus, rgbw, sk6812, ws2801, animations]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 01-01
|
||||||
|
provides: config.h constants (ANIM_FPS, BRIGHTNESS_CAP, BRIGHTNESS_MAX, LED counts, FreeRTOS priorities/stacks), led_driver.h strip API (stripWand, stripSchrank, applyBrightnessW/R, showBothStrips)
|
||||||
|
- phase: 01-02
|
||||||
|
provides: protocol.h (AnimParams, LedCommand, CmdType, Zone), wifi_server.h (commandQueue extern)
|
||||||
|
provides:
|
||||||
|
- AnimTickFn typedef and AnimEntry struct (animation_base.h)
|
||||||
|
- ANIM_REGISTRY with all 8 animation names (exact strings for Pi JSON commands)
|
||||||
|
- ZoneState struct and animation engine state machines (animation_engine.h)
|
||||||
|
- 50fps FreeRTOS task via vTaskDelayUntil (animation_engine.cpp)
|
||||||
|
- W-channel auto-derive formula: min(r, g, b) / 2 (animation_engine.cpp)
|
||||||
|
- All 8 animation implementations (animations/*.cpp)
|
||||||
|
- Startup breathing animation for D-11
|
||||||
|
affects: [01-04, phase-02-pi-app]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: [esp_random.h for hardware RNG in sparkle, math.h for sinf()]
|
||||||
|
patterns: [AnimTickFn-function-pointer-registry, zone-state-machine-50fps, autoWhite-W-derive]
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- firmware/src/animations/animation_base.h
|
||||||
|
- firmware/src/animation_engine.h
|
||||||
|
- firmware/src/animation_engine.cpp
|
||||||
|
- firmware/src/animations/chase.cpp
|
||||||
|
- firmware/src/animations/pulse.cpp
|
||||||
|
- firmware/src/animations/rainbow.cpp
|
||||||
|
- firmware/src/animations/strobe.cpp
|
||||||
|
- firmware/src/animations/color_wash.cpp
|
||||||
|
- firmware/src/animations/breathe.cpp
|
||||||
|
- firmware/src/animations/sparkle.cpp
|
||||||
|
- firmware/src/animations/gradient_sweep.cpp
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "autoWhite formula: min(r,g,b)/2 — warm-white approximation, only triggers when W=0 on RGBW strip"
|
||||||
|
- "vTaskDelayUntil (not vTaskDelay) for drift-free 50fps — accumulates timing debt correction across frames"
|
||||||
|
- "startup breathing at g_brightness=0.15f before WiFi connects — resets to BRIGHTNESS_CAP after WiFi up"
|
||||||
|
- "commandQueue drained non-blocking at start of each tick (not blocking) — animation frame never stalls"
|
||||||
|
- "sparkle uses esp_random() hardware RNG — no pseudo-random seed needed, avoids seeding issues"
|
||||||
|
- "breathe enforces 5% minimum brightness (never fully off) per D-11 alive indicator design"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "AnimTickFn pattern: function pointer signature (uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) used by all 8 animations"
|
||||||
|
- "isRGBW branch pattern: every animation handles RGBW (stripWand) and RGB (stripSchrank) via isRGBW param"
|
||||||
|
- "autoWhite call pattern: check w==0 before calling autoWhite() — explicit W values always honored"
|
||||||
|
- "brightness pattern: bright = params.intensity * g_brightness — never access BRIGHTNESS_MAX directly in animations"
|
||||||
|
|
||||||
|
requirements-completed: [FW-03, FW-04]
|
||||||
|
|
||||||
|
duration: 14min
|
||||||
|
completed: "2026-04-03"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 01 Plan 03: Animation Engine + 8 Animations Summary
|
||||||
|
|
||||||
|
**50fps FreeRTOS animation engine with ANIM_REGISTRY, zone state machines, W-channel auto-derive, and all 8 named animations (Chase/Pulse/Rainbow/Strobe/ColorWash/Breathe/Sparkle/GradientSweep) with palette-based RGBW support**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~14 min
|
||||||
|
- **Started:** 2026-04-03T11:23:05Z
|
||||||
|
- **Completed:** 2026-04-03T11:28:39Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files created:** 11
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Animation engine with 50fps FreeRTOS task using vTaskDelayUntil for drift-free timing
|
||||||
|
- ANIM_REGISTRY mapping 8 exact name strings to AnimTickFn pointers (used by Pi JSON "animation" field)
|
||||||
|
- W-channel auto-derive from RGB (D-02): `min(r, g, b) / 2` warm-white approximation
|
||||||
|
- All 8 animations implementing AnimTickFn signature with isRGBW dual-strip support
|
||||||
|
- Startup breathing animation (D-11): both zones breathe at 15% brightness before WiFi connects
|
||||||
|
|
||||||
|
## Animation Function Signatures and Parameters
|
||||||
|
|
||||||
|
All animations implement: `void animFoo(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW)`
|
||||||
|
|
||||||
|
| Animation | params.speed | params.intensity | params.colors | Special |
|
||||||
|
|-----------|-------------|-----------------|--------------|---------|
|
||||||
|
| chase | window position advance per tick | pixel brightness | colors[0] for window | window = ledCount/10 |
|
||||||
|
| pulse | sine wave tempo (0.1x factor) | brightness envelope amplitude | colors[0] solid fill | range 0-100% intensity |
|
||||||
|
| rainbow | hue offset advance rate | applied to HsbColor brightness | ignored (generates own) | uses HsbColor HSV |
|
||||||
|
| strobe | period = 20/speed ticks | on-state brightness | colors[0] for on state | min period = 2 ticks |
|
||||||
|
| color_wash | hold time = 50/speed per color | blend brightness | all colors, linear fade | wraps palette cyclically |
|
||||||
|
| breathe | sine envelope rate (0.05x factor) | envelope amplitude | colors[0] solid fill | 5% min brightness always |
|
||||||
|
| sparkle | not used directly | sparkle_count = ledCount*intensity*0.1 | colors[0] for sparkle | esp_random() hardware RNG |
|
||||||
|
| gradient_sweep | offset = tick*speed*3 | pixel brightness | colors[0], colors[1] blend | wraps if colorCount<2 |
|
||||||
|
|
||||||
|
## ANIM_REGISTRY — Exact Name Strings
|
||||||
|
|
||||||
|
These strings are used by the Pi-side app when sending JSON `"animation"` commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
"chase" → animChase
|
||||||
|
"pulse" → animPulse
|
||||||
|
"rainbow" → animRainbow
|
||||||
|
"strobe" → animStrobe
|
||||||
|
"color_wash" → animColorWash
|
||||||
|
"breathe" → animBreathe
|
||||||
|
"sparkle" → animSparkle
|
||||||
|
"gradient_sweep" → animGradientSweep
|
||||||
|
```
|
||||||
|
|
||||||
|
## W Auto-Derive Formula (D-02)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
uint8_t autoWhite(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
return std::min(r, std::min(g, b)) / 2;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Only triggered when `params.colors[i][3] == 0` on RGBW strips (isRGBW=true)
|
||||||
|
- Explicit W values (non-zero) always honored — user can override
|
||||||
|
- Formula: warm-white approximation — only produces white for near-neutral RGB inputs
|
||||||
|
- Phase 2 Pi-side: send `[R, G, B, 0]` to use auto-derive; send `[R, G, B, W]` to override
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
| Task | Name | Commit | Files |
|
||||||
|
|------|------|--------|-------|
|
||||||
|
| 1 | animation_base.h + animation_engine.h/.cpp | `230be99` (included in Plan 02 commit) | 3 files |
|
||||||
|
| 2 | 8 animation implementations | `cba4b17` | 8 files |
|
||||||
|
|
||||||
|
**Note:** Task 1 files were committed as part of Plan 02's final commit (`230be99`) because Plan 02's agent staged all untracked files in firmware/src/. The content is correct and matches the plan spec.
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `firmware/src/animations/animation_base.h` | AnimTickFn typedef, AnimEntry struct, 8 function declarations, ANIM_REGISTRY extern |
|
||||||
|
| `firmware/src/animation_engine.h` | ZoneState struct, g_brightness global, autoWhite() declaration, initAnimationEngine() |
|
||||||
|
| `firmware/src/animation_engine.cpp` | ANIM_REGISTRY[8], animTickTask (50fps vTaskDelayUntil), applyCommand, autoWhite impl, initAnimationEngine |
|
||||||
|
| `firmware/src/animations/chase.cpp` | Moving window animation, speed scales position advance, wraps strip end |
|
||||||
|
| `firmware/src/animations/pulse.cpp` | Sine wave brightness pulse on solid palette color |
|
||||||
|
| `firmware/src/animations/rainbow.cpp` | HSV rainbow via HsbColor, hue offset advances with tick, ignores palette |
|
||||||
|
| `firmware/src/animations/strobe.cpp` | On/off flicker, period = 20/speed ticks |
|
||||||
|
| `firmware/src/animations/color_wash.cpp` | Linear fade between palette colors, hold_ticks = 50/speed |
|
||||||
|
| `firmware/src/animations/breathe.cpp` | Slow sine envelope, 5% minimum brightness, D-11 alive indicator |
|
||||||
|
| `firmware/src/animations/sparkle.cpp` | Random pixel flash with esp_random() hardware RNG |
|
||||||
|
| `firmware/src/animations/gradient_sweep.cpp` | 2-color gradient sweeps with offset advance |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **vTaskDelayUntil over vTaskDelay:** Corrects for tick processing time — ensures exactly 50fps regardless of how long animations take to compute. vTaskDelay would drift by the compute time each frame.
|
||||||
|
- **Non-blocking commandQueue drain:** `xQueueReceive(..., 0)` with timeout=0 — never stalls the animation frame for an incoming command. Commands apply on the next frame, acceptable for animation purposes.
|
||||||
|
- **Direct applyCommand at init (not via queue):** `initAnimationEngine()` sets breathing animation directly on zone state, not via commandQueue, because the queue may not exist yet when init runs (before WiFi server starts).
|
||||||
|
- **sparkle uses esp_random():** ESP32 hardware RNG, no seeding required, better entropy than pseudo-random for visual effects.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] wifi_server.h existed with correct function signature**
|
||||||
|
|
||||||
|
Plan 03 expected to find `wifi_server.h` missing (Plan 02 is "parallel"). In practice, Plan 02 had already run and created `wifi_server.h` with `startUdpServer()` and `commandQueue` extern. No stub was needed — used the actual Plan 02 file. The initial stub creation via bash heredoc was overwritten by the already-committed Plan 02 version.
|
||||||
|
|
||||||
|
**2. [Rule 1 - Context] Task 1 files committed in Plan 02's commit**
|
||||||
|
|
||||||
|
Plan 02's agent staged and committed animation_engine.h/.cpp and animation_base.h as part of its commit `230be99`. The content matches Plan 03's spec exactly (no overwrite occurred). This is a parallel execution artifact — the files exist correctly in git with correct content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 2 (both non-impactful parallel execution artifacts)
|
||||||
|
**Impact:** Zero — all files contain correct content and are properly committed.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — all animation functions are fully implemented. No placeholder data flows to LEDs.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Plan 04 (integration) can wire `initAnimationEngine()` into `main.cpp` alongside `startUdpServer()`
|
||||||
|
- Animation engine is ready to consume LedCommand structs from commandQueue immediately
|
||||||
|
- Phase 2 Pi-side needs: ANIM_REGISTRY name strings (documented above), W auto-derive behavior (documented above), UDP port 4210, JSON protocol from protocol.h
|
||||||
|
- Startup breathing animation provides visual feedback — Plan 04 should restore BRIGHTNESS_CAP after WiFi connects
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 01-esp32-firmware*
|
||||||
|
*Completed: 2026-04-03*
|
||||||
Reference in New Issue
Block a user