Files
Claude d16445084a 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
2026-04-03 13:30:33 +02:00

9.9 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
01-esp32-firmware 03 firmware
esp32
animation-engine
freertos
neopixelbus
rgbw
sk6812
ws2801
animations
phase provides
01-01 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 provides
01-02 protocol.h (AnimParams, LedCommand, CmdType, Zone), wifi_server.h (commandQueue extern)
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
01-04
phase-02-pi-app
added patterns
esp_random.h for hardware RNG in sparkle
math.h for sinf()
AnimTickFn-function-pointer-registry
zone-state-machine-50fps
autoWhite-W-derive
created modified
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
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
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
FW-03
FW-04
14min 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 = ledCountintensity0.1 colors[0] for sparkle esp_random() hardware RNG
gradient_sweep offset = tickspeed3 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)

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