lib/libesp32/berry_animation/animation_docs/Troubleshooting.md
Common issues and solutions for the Tasmota Berry Animation Framework.
Note: This guide focuses on DSL usage, which is the recommended way to create animations. For programmatic API issues, see the Animation Development Guide.
Problem: import animation or import animation_dsl fails with "module not found"
Solutions:
Check Module Import:
import animation # Core framework
import animation_dsl # DSL compiler
Set Module Path:
berry -m lib/libesp32/berry_animation
Verify File Structure:
lib/libesp32/berry_animation/
├── animation.be # Main module file
├── dsl/ # DSL components
├── core/ # Core classes
├── animations/ # Animation effects
└── ...
Problem: Errors about missing tasmota or Leds classes
Solutions:
For Tasmota Environment:
For Development Environment:
# Mock Tasmota for testing
if !global.contains("tasmota")
global.tasmota = {
"millis": def() return 1000 end,
"scale_uint": def(val, from_min, from_max, to_min, to_max)
return int((val - from_min) * (to_max - to_min) / (from_max - from_min) + to_min)
end
}
end
Problem: DSL animations compile but LEDs don't change
Diagnostic Steps:
import animation
import animation_dsl
# Test basic DSL execution
var dsl_code = "color red = 0xFF0000\n" +
"animation red_anim = solid(color=red)\n" +
"run red_anim"
try
animation_dsl.execute(dsl_code)
print("DSL executed successfully")
except .. as e, msg
print("DSL Error:", msg)
end
Timing Behavior Note: The framework has updated timing behavior where:
start() method only resets the time origin if the animation/value provider was already started previouslyupdate(), render(), or produce_value() methodsCommon Solutions:
Missing Strip Declaration:
# Add explicit strip length if needed
strip length 30
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
Animation Not Executed:
# Make sure you have a 'run' statement
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim # Don't forget this!
Strip Auto-Detection Issues:
# Force strip length if auto-detection fails
strip length 30 # Must be first statement
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
Problem: Colors appear different than expected
Common Issues:
Missing Alpha Channel:
# Note: 0xFF0000 is valid RGB format (alpha defaults to 0xFF)
color red = 0xFF0000 # RGB format (alpha=255 assumed)
# Explicit alpha channel (ARGB format)
color red = 0xFFFF0000 # ARGB format (alpha=255, red=255)
color semi_red = 0x80FF0000 # ARGB format (alpha=128, red=255)
Color Format Confusion:
# ARGB format: 0xAARRGGBB
color red = 0xFFFF0000 # Alpha=FF, Red=FF, Green=00, Blue=00
color green = 0xFF00FF00 # Alpha=FF, Red=00, Green=FF, Blue=00
color blue = 0xFF0000FF # Alpha=FF, Red=00, Green=00, Blue=FF
Brightness Issues:
# Use opacity parameter or property assignment
animation red_anim = solid(color=red, opacity=255) # Full brightness
# Or assign after creation
animation pulse_red = breathe(color=red, period=2s)
pulse_red.opacity = 200 # Adjust brightness
# Use value providers for dynamic brightness
set brightness = smooth(min_value=50, max_value=255, period=3s)
animation breathing = solid(color=red)
breathing.opacity = brightness
Problem: Animation timing doesn't match expectations
Solutions:
Check Time Units:
# DSL uses time units (converted to milliseconds)
animation pulse_anim = breathe(color=red, period=2s) # 2 seconds
animation fast_pulse = breathe(color=blue, period=500ms) # 0.5 seconds
Adjust Periods:
# Too fast - increase period
animation slow_pulse = breathe(color=red, period=5s) # 5 seconds
# Too slow - decrease period
animation fast_pulse = breathe(color=red, period=500ms) # 0.5 seconds
Performance Limitations:
# Use sequences instead of multiple simultaneous animations
sequence optimized_show {
play animation1 for 3s
play animation2 for 3s
play animation3 for 3s
}
run optimized_show
# Instead of:
# run animation1
# run animation2
# run animation3
Problem: DSL code fails to compile
Diagnostic Approach:
try
var berry_code = animation_dsl.compile(dsl_source)
print("Compilation successful")
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
end
Common DSL Errors:
Undefined Colors:
# Wrong - color not defined
animation red_anim = solid(color=red)
# Correct - define color first
color red = 0xFF0000
animation red_anim = solid(color=red)
Invalid Color Format:
# Wrong - # prefix not supported (conflicts with comments)
color red = #FF0000
# Correct - use 0x prefix
color red = 0xFF0000
Missing Time Units:
# Wrong - no time unit
animation pulse_anim = breathe(color=red, period=2000)
# Correct - with time unit
animation pulse_anim = breathe(color=red, period=2s)
Reserved Name Conflicts:
# Wrong - 'red' is a predefined color
color red = 0x800000
# Correct - use different name
color dark_red = 0x800000
Invalid Parameter Names:
# Wrong - invalid parameter name
animation pulse_anim = breathe(color=red, invalid_param=123)
# Error: "Parameter 'invalid_param' is not valid for breathe"
# Correct - use valid parameters (see Dsl_Reference.md for complete list)
animation pulse_anim = breathe(color=red, period=2s)
Variable Duration Support:
# Now supported - variables in play/wait durations
set eye_duration = 5s
sequence cylon_eye {
play red_eye for eye_duration # ✓ Variables now work
wait eye_duration # ✓ Variables work in wait too
}
# Also supported - value providers for dynamic duration
set dynamic_time = triangle(min_value=1000, max_value=3000, period=10s)
sequence demo {
play animation for dynamic_time # ✓ Dynamic duration
}
Template Definition Errors:
# Wrong - missing braces
template pulse_effect
param color type color
param speed
# Error: Expected '{' after template name
# Wrong - invalid parameter syntax
template pulse_effect {
param color as color # Error: Use 'type' instead of 'as'
param speed
}
# Wrong - missing template body
template pulse_effect {
param color type color
}
# Error: Template body cannot be empty
# Correct - proper template syntax
template pulse_effect {
param color type color
param speed
animation pulse = breathe(
color=color
period=speed
)
run pulse
}
Template Call Errors:
# Wrong - template not defined
pulse_effect(red, 2s)
# Error: "Undefined reference: 'pulse_effect'"
# Wrong - incorrect parameter count
template pulse_effect {
param color type color
param speed
# ... template body ...
}
pulse_effect(red) # Error: Expected 2 parameters, got 1
# Correct - define template first, call with correct parameters
template pulse_effect {
param color type color
param speed
animation pulse = breathe(color=color, period=speed)
run pulse
}
pulse_effect(red, 2s) # ✓ Correct usage
Parameter Constraint Violations:
# Wrong - negative period not allowed
animation bad_pulse = breathe(color=red, period=-2s)
# Error: "Parameter 'period' value -2000 violates constraint: min=1"
# Wrong - invalid enum value
animation bad_comet = comet(color=red, direction=5)
# Error: "Parameter 'direction' value 5 not in allowed values: [-1, 1]"
# Correct - valid parameters within constraints
animation good_pulse = breathe(color=red, period=2s)
animation good_comet = comet(color=red, direction=1)
Repeat Syntax Errors:
# Wrong - old colon syntax no longer supported
sequence bad_demo {
repeat 3 times: # Error: Expected '{' after 'times'
play anim for 1s
}
# Wrong - missing braces
sequence bad_demo2 {
repeat 3 times
play anim for 1s # Error: Expected '{' after 'times'
}
# Correct - use braces for repeat blocks
sequence good_demo {
repeat 3 times {
play anim for 1s
}
}
# Also correct - alternative syntax
sequence good_demo_alt repeat 3 times {
play anim for 1s
}
# Correct - forever syntax
sequence infinite_demo {
repeat forever {
play anim for 1s
wait 500ms
}
}
Problem: Template definitions fail to compile
Common Template Errors:
Missing Template Body:
# Wrong - empty template
template empty_template {
param color type color
}
# Error: "Template body cannot be empty"
# Correct - template must have content
template pulse_effect {
param color type color
param speed
animation pulse = breathe(color=color, period=speed)
run pulse
}
Invalid Parameter Syntax:
# Wrong - old 'as' syntax
template pulse_effect {
param color as color
}
# Error: Expected 'type' keyword, got 'as'
# Correct - use 'type' keyword
template pulse_effect {
param color type color
param speed # Type annotation is optional
}
Template Name Conflicts:
# Wrong - template name conflicts with built-in function
template solid { # 'solid' is a built-in animation function
param color type color
# ...
}
# Error: "Template name 'solid' conflicts with built-in function"
# Correct - use unique template names
template solid_effect {
param color type color
# ...
}
Problem: Template calls fail or behave unexpectedly
Common Issues:
Undefined Template:
# Wrong - calling undefined template
my_effect(red, 2s)
# Error: "Undefined reference: 'my_effect'"
# Correct - define template first
template my_effect {
param color type color
param speed
# ... template body ...
}
my_effect(red, 2s) # Now works
Parameter Count Mismatch:
template pulse_effect {
param color type color
param speed
param brightness
}
# Wrong - missing parameters
pulse_effect(red, 2s) # Error: Expected 3 parameters, got 2
# Correct - provide all parameters
pulse_effect(red, 2s, 200)
Parameter Type Issues:
template pulse_effect {
param color type color
param speed
}
# Wrong - invalid color parameter
pulse_effect("not_a_color", 2s)
# Runtime error: Invalid color value
# Correct - use valid color
pulse_effect(red, 2s) # Named color
pulse_effect(0xFF0000, 2s) # Hex color
Problem: Mixing template and user function concepts
Key Differences:
# Template (DSL-native) - Recommended for most cases
template pulse_effect {
param color type color
param speed
animation pulse = breathe(color=color, period=speed)
run pulse
}
# User Function (Berry-native) - For complex logic
def create_pulse_effect(engine, color, speed)
var pulse = animation.breathe(engine)
pulse.color = color
pulse.period = speed
return pulse
end
animation.register_user_function("pulse_effect", create_pulse_effect)
When to Use Each:
Problem: DSL compiles but fails at runtime
Common Issues:
Strip Not Initialized:
# Add strip declaration if needed
strip length 30
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
Repeat Performance Issues:
# Efficient - runtime repeats don't expand at compile time
sequence efficient {
repeat 1000 times { # No memory overhead for large counts
play anim for 100ms
wait 50ms
}
}
# Nested repeats work efficiently
sequence nested {
repeat 100 times {
repeat 50 times { # Total: 5000 iterations, but efficient
play quick_flash for 10ms
}
wait 100ms
}
}
Sequence Issues:
# Make sure animations are defined before sequences
color red = 0xFF0000
animation red_anim = solid(color=red) # Define first
sequence demo {
play red_anim for 3s # Use after definition
wait 1s # Optional pause between animations
}
run demo
Undefined References:
# Wrong - using undefined animation in sequence
sequence bad_demo {
play undefined_animation for 3s
}
# Error: "Undefined reference: 'undefined_animation'"
# Correct - define all references first
color blue = 0x0000FF
animation blue_anim = solid(color=blue)
sequence good_demo {
play blue_anim for 3s
}
run good_demo
Feature: Built-in CPU metrics tracking to monitor animation performance
The AnimationEngine automatically tracks CPU usage and provides detailed statistics every 5 seconds. This helps identify performance bottlenecks and optimize animations for ESP32 embedded systems.
Automatic Metrics:
When the engine is running, it automatically logs performance statistics:
AnimEngine: ticks=1000/1000 missed=0 total=0.50ms(0-2) anim=0.30ms(0-1) hw=0.20ms(0-1) cpu=10.0%
Phase1(checks): mean=0.05ms(0-0)
Phase2(events): mean=0.05ms(0-0)
Phase3(anim): mean=0.20ms(0-1)
Metrics Explained:
Phase Metrics (Optional): When intermediate measurement points are available, the engine also reports phase-based timing:
Timestamp-Based Profiling:
The engine uses a timestamp-based profiling system that stores only timestamps (not durations) in instance variables:
ts_start - Tick start timestampts_1 - After initial checks (optional)ts_2 - After event processing (optional)ts_3 - After animation update/render (optional)ts_hw - After hardware outputts_end - Tick end timestampDurations are computed from these timestamps in _record_tick_metrics() with nil checks to ensure values are valid.
Accessing Profiling Data:
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
# Add an animation
var anim = animation.solid(engine)
anim.color = 0xFFFF0000
engine.add(anim)
engine.run()
# Run for a while to collect metrics
# After 5 seconds, metrics are automatically logged
# Access current metrics programmatically
print("Tick count:", engine.tick_count)
print("Total time sum:", engine.tick_time_sum)
print("Animation time sum:", engine.anim_time_sum)
print("Hardware time sum:", engine.hw_time_sum)
# Access phase metrics if available
if engine.phase1_time_sum > 0
print("Phase 1 time sum:", engine.phase1_time_sum)
end
Profiling Benefits:
Memory Efficient:
Automatic Tracking:
Detailed Breakdown:
Interpreting Performance Metrics:
High Animation Time:
Solution: Simplify animations or use sequences
High Hardware Time:
Solution: Reduce update frequency or strip length
Missed Ticks:
Solution: Optimize animations or reduce complexity
High CPU Percentage:
Solution: Increase animation periods or reduce effects
Example Performance Optimization:
import animation
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Before optimization - complex animation
var complex_anim = animation.rainbow_animation(engine)
complex_anim.period = 100 # Very fast, high CPU
engine.add(complex_anim)
engine.run()
# Check metrics after 5 seconds:
# AnimEngine: ticks=950/1000 missed=50 total=5.2ms(4-8) cpu=104.0%
# ^ Too slow! Missing ticks and over 100% CPU
# After optimization - slower period
complex_anim.period = 2000 # 2 seconds instead of 100ms
# Check metrics after 5 seconds:
# AnimEngine: ticks=1000/1000 missed=0 total=0.8ms(0-2) cpu=16.0%
# ^ Much better! All ticks processed, reasonable CPU usage
Problem: Animations appear jerky or stuttering
Solutions:
Use Sequences Instead of Multiple Animations:
# Good - sequential playback
sequence smooth_show {
play animation1 for 3s
play animation2 for 3s
play animation3 for 3s
}
run smooth_show
# Avoid - too many simultaneous animations
# run animation1
# run animation2
# run animation3
Increase Animation Periods:
# Smooth - longer periods
animation smooth_pulse = breathe(color=red, period=3s)
# Choppy - very short periods
animation choppy_pulse = breathe(color=red, period=50ms)
Optimize Value Providers:
# Efficient - reuse providers
set breathing = smooth(min_value=50, max_value=255, period=2s)
color red = 0xFF0000
color blue = 0x0000FF
animation anim1 = breathe(color=red, period=2s)
anim1.opacity = breathing
animation anim2 = breathe(color=blue, period=2s)
anim2.opacity = breathing # Reuse same provider
Monitor CPU Metrics:
# Check if CPU is overloaded
# Look for missed ticks or high CPU percentage in metrics
# AnimEngine: ticks=950/1000 missed=50 ... cpu=95.0%
# ^ This indicates performance issues
# Use profiling to find bottlenecks
engine.profile_start("suspect_code")
# ... code that might be slow ...
engine.profile_end("suspect_code")
Problem: Out of memory errors or system crashes
Solutions:
Clear Unused Animations:
# Clear before adding new animations
engine.clear()
engine.add(new_animation)
Limit Palette Size:
# Good - reasonable palette size
palette simple_fire = [
(0, 0x000000),
(128, 0xFF0000),
(255, 0xFFFF00)
]
# Avoid - very large palettes
# palette huge_palette = [
# (0, color1), (1, color2), ... (255, color256)
# ]
Use Sequences Instead of Simultaneous Animations:
# Memory efficient - sequential playback
sequence show {
play animation1 for 5s
play animation2 for 5s
play animation3 for 5s
}
# Memory intensive - all at once
# run animation1
# run animation2
# run animation3
Problem: Event handlers don't execute
Diagnostic Steps:
# Check if handler is registered
var handlers = animation.get_event_handlers("button_press")
print("Handler count:", size(handlers))
# Test event triggering
animation.trigger_event("test_event", {"debug": true})
Solutions:
Verify Handler Registration:
def test_handler(event_data)
print("Event triggered:", event_data)
end
var handler = animation.register_event_handler("test", test_handler, 0)
print("Handler registered:", handler != nil)
Check Event Names:
# Event names are case-sensitive
animation.register_event_handler("button_press", handler) # Correct
animation.trigger_event("button_press", {}) # Must match exactly
Verify Conditions:
def condition_func(event_data)
return event_data.contains("required_field")
end
animation.register_event_handler("event", handler, 0, condition_func)
# Event data must satisfy condition
animation.trigger_event("event", {"required_field": "value"})
Problem: Framework runs but LEDs don't light up
Hardware Checks:
Power Supply:
Wiring:
LED Strip:
Software Checks:
# Test basic LED functionality
var strip = Leds(30) # 30 LEDs
strip.set_pixel_color(0, 0xFFFF0000) # Set first pixel red
strip.show() # Update LEDs
# Test with animation framework
import animation
var engine = animation.create_engine(strip)
var red_anim = animation.solid(engine)
red_anim.color = 0xFFFF0000
engine.add(red_anim)
engine.run()
# If basic strip works but animation doesn't, check framework setup
Problem: Colors look different on actual LEDs vs. expected
Solutions:
Color Order:
# Some strips use different color orders
# Try different strip types in Tasmota configuration
# WS2812: RGB order
# SK6812: GRBW order
Gamma Correction:
# Enable gamma correction in Tasmota
# SetOption37 128 # Enable gamma correction
Power Supply Issues:
For DSL Issues (Recommended):
# Enable DSL debug output
import animation_dsl
var dsl_code = "color red = 0xFF0000\nanimation test = solid(color=red)\nrun test"
# Check compilation
try
var berry_code = animation_dsl.compile(dsl_code)
print("DSL compilation successful")
print("Generated Berry code:")
print(berry_code)
except .. as e, msg
print("DSL compilation error:", msg)
end
# Execute with debug
try
animation_dsl.execute(dsl_code, true) # debug=true
except .. as e, msg
print("DSL execution error:", msg)
end
For Framework Issues (Advanced):
# Direct Berry API debugging (for framework developers)
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip, true) # debug=true
var anim = animation.solid(engine)
anim.color = 0xFFFF0000
engine.add(anim)
engine.run()
# Test each component individually
print("1. Creating strip...")
var strip = Leds(30)
print("Strip created:", strip != nil)
print("2. Creating engine...")
var engine = animation.create_engine(strip)
print("Engine created:", engine != nil)
print("3. Creating animation...")
var anim = animation.solid(engine)
anim.color = 0xFFFF0000
print("Animation created:", anim != nil)
print("4. Adding animation...")
engine.add(anim)
print("Animation count:", engine.size())
print("5. Starting engine...")
engine.run()
print("Engine active:", engine.is_active())
# Check timing
var start_time = tasmota.millis()
# ... run animation code ...
var end_time = tasmota.millis()
print("Execution time:", end_time - start_time, "ms")
# Monitor memory (if available)
import gc
print("Memory before:", gc.allocated())
# ... create animations ...
print("Memory after:", gc.allocated())
When asking for help, include:
Hardware Setup:
Software Environment:
Code:
Debugging Output:
**Problem:** DSL animation compiles but LEDs don't change
**Hardware:**
- 30x WS2812 LEDs on GPIO 1
- ESP32 with 5V/2A power supply
**Code:**
```berry
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
Error Output:
DSL compilation successful
Engine created: true
Animation count: 1
Engine active: true
Expected: LEDs turn red Actual: LEDs remain off
Additional Info:
strip.set_pixel_color(0, 0xFFFF0000); strip.show() works
This format helps identify issues quickly and provide targeted solutions.
## Prevention Tips
### Code Quality
1. **Use Try-Catch Blocks:**
```berry
try
runtime.load_dsl(dsl_code)
except .. as e, msg
print("Error:", msg)
end
Validate Inputs:
if type(color) == "int" && color >= 0
var anim = animation.solid(color)
else
print("Invalid color:", color)
end
Test Incrementally:
Limit Complexity:
Resource Management:
Hardware Considerations:
color red = 0xFF0000
animation red_solid = solid(color=red)
run red_solid
# Define reusable template
template pulse_effect {
param base_color type color # Use descriptive names
param speed type time # Add type annotations for clarity
animation pulse = breathe(color=base_color, period=speed)
run pulse
}
# Use template multiple times
pulse_effect(red, 2s)
pulse_effect(blue, 1s)
Common Template Parameter Issues:
# ❌ AVOID: Parameter name conflicts
template bad_example {
param color type color # Error: conflicts with built-in color name
param animation type number # Error: conflicts with reserved keyword
}
# ✅ CORRECT: Use descriptive, non-conflicting names
template good_example {
param base_color type color # Clear, non-conflicting name
param anim_speed type time # Descriptive parameter name
}
# ⚠️ WARNING: Unused parameters generate warnings
template unused_param_example {
param used_color type color
param unused_value type number # Warning: never used in template body
animation test = solid(color=used_color)
run test
}
color blue = 0x0000FF
animation blue_pulse = breathe(color=blue, period=2s, opacity=200)
run blue_pulse
set breathing = smooth(min_value=50, max_value=255, period=3s)
color green = 0x00FF00
animation breathing_green = solid(color=green)
breathing_green.opacity = breathing
run breathing_green
color red = 0xFF0000
color blue = 0x0000FF
animation red_anim = solid(color=red)
animation blue_anim = solid(color=blue)
sequence demo {
play red_anim for 2s
wait 500ms
play blue_anim for 2s
}
run demo
strip length 60 # Must be first statement
color rainbow = rainbow_color_provider(period=5s)
animation rainbow_anim = solid(color=rainbow)
run rainbow_anim
Following these guidelines will help you avoid most common issues and create reliable LED animations.