Force remove all-caps doc files to avoid case conflict

This commit is contained in:
s-hadinger 2025-12-27 14:18:34 +01:00 committed by GitHub
parent 7a0c639bd4
commit 824d5bb985
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 0 additions and 8157 deletions

View File

@ -1,695 +0,0 @@
# Animation Development Guide
Guide for developers creating custom animation classes in the Berry Animation Framework.
## Overview
**Note**: This guide is for developers who want to extend the framework by creating new animation classes. For using existing animations, see the [DSL Reference](DSL_REFERENCE.md) which provides a declarative way to create animations without programming.
The Berry Animation Framework uses a unified architecture where all visual elements inherit from the base `Animation` class. This guide explains how to create custom animation classes that integrate seamlessly with the framework's parameter system, value providers, and rendering pipeline.
## Animation Class Structure
### Basic Class Template
```berry
#@ solidify:MyAnimation,weak
class MyAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions following the new specification
static var PARAMS = {
"my_param1": {"default": "default_value", "type": "string"},
"my_param2": {"min": 0, "max": 255, "default": 100, "type": "int"}
# Do NOT include inherited Animation parameters here
}
def init(engine)
# Engine parameter is MANDATORY and cannot be nil
super(self).init(engine)
# Only initialize non-parameter instance variables (none in this example)
# Parameters are handled by the virtual parameter system
end
# Handle parameter changes (optional)
def on_param_changed(name, value)
# Add custom logic for parameter changes if needed
# Parameter validation is handled automatically by the framework
end
# Update animation state (no return value needed)
def update(time_ms)
super(self).update(time_ms)
# Your update logic here
end
def render(frame, time_ms, strip_length)
if !self.is_running || frame == nil
return false
end
# Use virtual parameter access - automatically resolves ValueProviders
var param1 = self.my_param1
var param2 = self.my_param2
# Use strip_length parameter instead of self.engine.strip_length for performance
# Your rendering logic here
# ...
return true
end
# NO setter methods needed - use direct virtual parameter assignment:
# obj.my_param1 = value
# obj.my_param2 = value
def tostring()
return f"MyAnimation(param1={self.my_param1}, param2={self.my_param2}, running={self.is_running})"
end
end
```
## PARAMS System
### Static Parameter Definition
The `PARAMS` static variable defines all parameters specific to your animation class. This system provides:
- **Parameter validation** with min/max constraints and type checking
- **Default value handling** for initialization
- **Virtual parameter access** through getmember/setmember
- **Automatic ValueProvider resolution**
#### Parameter Definition Format
```berry
static var PARAMS = {
"parameter_name": {
"default": default_value, # Default value (optional)
"min": minimum_value, # Minimum value for integers (optional)
"max": maximum_value, # Maximum value for integers (optional)
"enum": [val1, val2, val3], # Valid enum values (optional)
"type": "parameter_type", # Expected type (optional)
"nillable": true # Whether nil values are allowed (optional)
}
}
```
#### Supported Types
- **`"int"`** - Integer values (default if not specified)
- **`"string"`** - String values
- **`"bool"`** - Boolean values (true/false)
- **`"bytes"`** - Bytes objects (validated using isinstance())
- **`"instance"`** - Object instances
- **`"any"`** - Any type (no type validation)
#### Important Rules
- **Do NOT include inherited parameters** - Animation base class parameters are handled automatically
- **Only define class-specific parameters** in your PARAMS
- **No constructor parameter mapping** - the new system uses engine-only constructors
- **Parameters are accessed via virtual members**: `obj.param_name`
## Constructor Implementation
### Engine-Only Constructor Pattern
```berry
def init(engine)
# 1. ALWAYS call super with engine (engine is the ONLY parameter)
super(self).init(engine)
# 2. Initialize non-parameter instance variables only
self.internal_state = initial_value
self.buffer = nil
# Do NOT initialize parameters here - they are handled by the virtual system
end
```
### Parameter Change Handling
```berry
def on_param_changed(name, value)
# Optional method to handle parameter changes
if name == "scale"
# Recalculate internal state when scale changes
self._update_internal_buffers()
elif name == "color"
# Handle color changes
self._invalidate_color_cache()
end
end
```
### Key Changes from Old System
- **Engine-only constructor**: Constructor takes ONLY the engine parameter
- **No parameter initialization**: Parameters are set by caller using virtual member assignment
- **No instance variables for parameters**: Parameters are handled by the virtual system
- **Automatic validation**: Parameter validation happens automatically based on PARAMS constraints
## Value Provider Integration
### Automatic ValueProvider Resolution
The virtual parameter system automatically resolves ValueProviders when you access parameters:
```berry
def render(frame, time_ms, strip_length)
# Virtual parameter access automatically resolves ValueProviders
var color = self.color # Returns current color value, not the provider
var position = self.pos # Returns current position value
var size = self.size # Returns current size value
# Use strip_length parameter (computed once by engine_proxy) instead of self.engine.strip_length
# Use resolved values in rendering logic
for i: position..(position + size - 1)
if i >= 0 && i < strip_length
frame.set_pixel_color(i, color)
end
end
return true
end
```
### Setting Dynamic Parameters
Users can set both static values and ValueProviders using the same syntax:
```berry
# Create animation
var anim = animation.my_animation(engine)
# Static values
anim.color = 0xFFFF0000
anim.pos = 5
anim.size = 3
# Dynamic values
anim.color = animation.smooth(0xFF000000, 0xFFFFFFFF, 2000)
anim.pos = animation.triangle(0, 29, 3000)
```
### Performance Optimization
For performance-critical code, cache parameter values:
```berry
def render(frame, time_ms, strip_length)
# Cache parameter values to avoid multiple virtual member access
var current_color = self.color
var current_pos = self.pos
var current_size = self.size
# Use cached values in loops
for i: current_pos..(current_pos + current_size - 1)
if i >= 0 && i < strip_length
frame.set_pixel_color(i, current_color)
end
end
return true
end
```
### Color Provider LUT Optimization
For color providers that perform expensive color calculations (like palette interpolation), the base `ColorProvider` class provides a Lookup Table (LUT) mechanism for caching pre-computed colors:
```berry
#@ solidify:MyColorProvider,weak
class MyColorProvider : animation.color_provider
# Instance variables (all should start with underscore)
var _cached_data # Your custom cached data
def init(engine)
super(self).init(engine) # Initializes _color_lut and _lut_dirty
self._cached_data = nil
end
# Mark LUT as dirty when parameters change
def on_param_changed(name, value)
super(self).on_param_changed(name, value)
if name == "colors" || name == "transition_type"
self._lut_dirty = true # Inherited from ColorProvider
end
end
# Rebuild LUT when needed
def _rebuild_color_lut()
# Allocate LUT (e.g., 129 entries * 4 bytes = 516 bytes)
if self._color_lut == nil
self._color_lut = bytes()
self._color_lut.resize(129 * 4)
end
# Pre-compute colors for values 0, 2, 4, ..., 254, 255
var i = 0
while i < 128
var value = i * 2
var color = self._compute_color_expensive(value)
self._color_lut.set(i * 4, color, 4)
i += 1
end
# Add final entry for value 255
var color_255 = self._compute_color_expensive(255)
self._color_lut.set(128 * 4, color_255, 4)
self._lut_dirty = false
end
# Update method checks if LUT needs rebuilding
def update(time_ms)
if self._lut_dirty || self._color_lut == nil
self._rebuild_color_lut()
end
return self.is_running
end
# Fast color lookup using LUT
def get_color_for_value(value, time_ms)
# Build LUT if needed (lazy initialization)
if self._lut_dirty || self._color_lut == nil
self._rebuild_color_lut()
end
# Map value to LUT index (divide by 2, special case for 255)
var lut_index = value >> 1
if value >= 255
lut_index = 128
end
# Retrieve pre-computed color from LUT
var color = self._color_lut.get(lut_index * 4, 4)
# Apply brightness scaling using static method (only if not 255)
var brightness = self.brightness
if brightness != 255
return animation.color_provider.apply_brightness(color, brightness)
end
return color
end
# Access LUT from outside (returns bytes() or nil)
# Inherited from ColorProvider: get_lut()
end
```
**LUT Benefits:**
- **5-10x speedup** for expensive color calculations
- **Reduced CPU usage** during rendering
- **Smooth animations** even with complex color logic
- **Memory efficient** (typically 516 bytes for 129 entries)
**When to use LUT:**
- Palette interpolation with binary search
- Complex color transformations
- Brightness calculations
- Any expensive per-pixel color computation
**LUT Guidelines:**
- Store colors at maximum brightness, apply scaling after lookup
- Use 2-step resolution (0, 2, 4, ..., 254, 255) to save memory
- Invalidate LUT when parameters affecting color calculation change
- Don't invalidate for brightness changes if brightness is applied post-lookup
**Brightness Handling:**
The `ColorProvider` base class includes a `brightness` parameter (0-255, default 255) and a static method for applying brightness scaling:
```berry
# Static method for brightness scaling (only scales if brightness != 255)
animation.color_provider.apply_brightness(color, brightness)
```
**Best Practices:**
- Store LUT colors at maximum brightness (255)
- Apply brightness scaling after LUT lookup using the static method
- Only call the static method if `brightness != 255` to avoid unnecessary overhead
- For inline performance-critical code, you can inline the brightness calculation instead of calling the static method
- Brightness changes do NOT invalidate the LUT since brightness is applied after lookup
## Parameter Access
### Direct Virtual Member Assignment
The new system uses direct parameter assignment instead of setter methods:
```berry
# Create animation
var anim = animation.my_animation(engine)
# Direct parameter assignment (recommended)
anim.color = 0xFF00FF00
anim.pos = 10
anim.size = 5
# Method chaining is not needed - just set parameters directly
```
### Parameter Validation
The parameter system handles validation automatically based on PARAMS constraints:
```berry
# This will raise an exception due to min: 0 constraint
anim.size = -1 # Raises value_error
# This will be accepted
anim.size = 5 # Parameter updated successfully
# Method-based setting returns true/false for validation
var success = anim.set_param("size", -1) # Returns false, no exception
```
### Accessing Raw Parameters
```berry
# Get current parameter value (resolved if ValueProvider)
var current_color = anim.color
# Get raw parameter (returns ValueProvider if set)
var raw_color = anim.get_param("color")
# Check if parameter is a ValueProvider
if animation.is_value_provider(raw_color)
print("Color is dynamic")
else
print("Color is static")
end
```
## Rendering Implementation
### Frame Buffer Operations
```berry
def render(frame, time_ms, strip_length)
if !self.is_running || frame == nil
return false
end
# Resolve dynamic parameters
var color = self.resolve_value(self.color, "color", time_ms)
var opacity = self.resolve_value(self.opacity, "opacity", time_ms)
# Render your effect using strip_length parameter
for i: 0..(strip_length-1)
var pixel_color = calculate_pixel_color(i, time_ms)
frame.set_pixel_color(i, pixel_color)
end
# Apply opacity if not full (supports numbers, animations)
if opacity < 255
frame.apply_opacity(opacity)
end
return true # Frame was modified
end
```
### Common Rendering Patterns
#### Fill Pattern
```berry
# Fill entire frame with color
frame.fill_pixels(color)
```
#### Position-Based Effects
```berry
# Render at specific positions
var start_pos = self.resolve_value(self.pos, "pos", time_ms)
var size = self.resolve_value(self.size, "size", time_ms)
for i: 0..(size-1)
var pixel_pos = start_pos + i
if pixel_pos >= 0 && pixel_pos < frame.width
frame.set_pixel_color(pixel_pos, color)
end
end
```
#### Gradient Effects
```berry
# Create gradient across frame
for i: 0..(frame.width-1)
var progress = i / (frame.width - 1.0) # 0.0 to 1.0
var interpolated_color = interpolate_color(start_color, end_color, progress)
frame.set_pixel_color(i, interpolated_color)
end
```
## Complete Example: BeaconAnimation
Here's a complete example showing all concepts:
```berry
#@ solidify:BeaconAnimation,weak
class BeaconAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions following the new specification
static var PARAMS = {
"color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000},
"pos": {"default": 0},
"beacon_size": {"min": 0, "default": 1},
"slew_size": {"min": 0, "default": 0}
}
# Initialize a new Pulse Position animation
# Engine parameter is MANDATORY and cannot be nil
def init(engine)
# Call parent constructor with engine (engine is the ONLY parameter)
super(self).init(engine)
# Only initialize non-parameter instance variables (none in this case)
# Parameters are handled by the virtual parameter system
end
# Handle parameter changes (optional - can be removed if no special handling needed)
def on_param_changed(name, value)
# No special handling needed for this animation
# Parameter validation is handled automatically by the framework
end
# Render the pulse to the provided frame buffer
def render(frame, time_ms, strip_length)
if frame == nil
return false
end
var pixel_size = strip_length
# Use virtual parameter access - automatically resolves ValueProviders
var back_color = self.back_color
var pos = self.pos
var slew_size = self.slew_size
var beacon_size = self.beacon_size
var color = self.color
# Fill background if not transparent
if back_color != 0xFF000000
frame.fill_pixels(back_color)
end
# Calculate pulse boundaries
var pulse_min = pos
var pulse_max = pos + beacon_size
# Clamp to frame boundaries
if pulse_min < 0
pulse_min = 0
end
if pulse_max >= pixel_size
pulse_max = pixel_size
end
# Draw the main pulse
var i = pulse_min
while i < pulse_max
frame.set_pixel_color(i, color)
i += 1
end
# Draw slew regions if slew_size > 0
if slew_size > 0
# Left slew (fade from background to pulse color)
var left_slew_min = pos - slew_size
var left_slew_max = pos
if left_slew_min < 0
left_slew_min = 0
end
if left_slew_max >= pixel_size
left_slew_max = pixel_size
end
i = left_slew_min
while i < left_slew_max
# Calculate blend factor
var blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0)
var alpha = 255 - blend_factor
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
var blended_color = frame.blend(back_color, blend_color)
frame.set_pixel_color(i, blended_color)
i += 1
end
# Right slew (fade from pulse color to background)
var right_slew_min = pos + beacon_size
var right_slew_max = pos + beacon_size + slew_size
if right_slew_min < 0
right_slew_min = 0
end
if right_slew_max >= pixel_size
right_slew_max = pixel_size
end
i = right_slew_min
while i < right_slew_max
# Calculate blend factor
var blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255)
var alpha = 255 - blend_factor
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
var blended_color = frame.blend(back_color, blend_color)
frame.set_pixel_color(i, blended_color)
i += 1
end
end
return true
end
# NO setter methods - use direct virtual parameter assignment instead:
# obj.color = value
# obj.pos = value
# obj.beacon_size = value
# obj.slew_size = value
# String representation of the animation
def tostring()
return f"BeaconAnimation(color=0x{self.color :08x}, pos={self.pos}, beacon_size={self.beacon_size}, slew_size={self.slew_size})"
end
end
# Export class directly - no redundant factory function needed
return {'beacon_animation': BeaconAnimation}
```
## Testing Your Animation
### Unit Tests
Create comprehensive tests for your animation:
```berry
import animation
def test_my_animation()
# Create LED strip and engine for testing
var strip = global.Leds(10) # Use built-in LED strip for testing
var engine = animation.create_engine(strip)
# Test basic construction
var anim = animation.my_animation(engine)
assert(anim != nil, "Animation should be created")
# Test parameter setting
anim.color = 0xFFFF0000
assert(anim.color == 0xFFFF0000, "Color should be set")
# Test parameter updates
anim.color = 0xFF00FF00
assert(anim.color == 0xFF00FF00, "Color should be updated")
# Test value providers
var dynamic_color = animation.smooth(engine)
dynamic_color.min_value = 0xFF000000
dynamic_color.max_value = 0xFFFFFFFF
dynamic_color.duration = 2000
anim.color = dynamic_color
var raw_color = anim.get_param("color")
assert(animation.is_value_provider(raw_color), "Should accept value provider")
# Test rendering
var frame = animation.frame_buffer(10)
anim.start()
var result = anim.render(frame, 1000, engine.strip_length)
assert(result == true, "Should render successfully")
print("✓ All tests passed")
end
test_my_animation()
```
### Integration Testing
Test with the animation engine:
```berry
var strip = global.Leds(30) # Use built-in LED strip
var engine = animation.create_engine(strip)
var anim = animation.my_animation(engine)
# Set parameters
anim.color = 0xFFFF0000
anim.pos = 5
anim.beacon_size = 3
engine.add(anim) # Unified method for animations and sequence managers
engine.run()
# Let it run for a few seconds
tasmota.delay(3000)
engine.stop()
print("Integration test completed")
```
## Best Practices
### Performance
- **Minimize calculations** in render() method
- **Cache resolved values** when possible
- **Use integer math** instead of floating point
- **Avoid memory allocation** in render loops
### Memory Management
- **Reuse objects** when possible
- **Clear references** to large objects when done
- **Use static variables** for constants
### Code Organization
- **Group related parameters** together
- **Use descriptive variable names**
- **Comment complex algorithms**
- **Follow Berry naming conventions**
### Error Handling
- **Validate parameters** in constructor
- **Handle edge cases** gracefully
- **Return false** from render() on errors
- **Use meaningful error messages**
## Publishing Your Animation Class
Once you've created a new animation class:
1. **Add it to the animation module** by importing it in `animation.be`
2. **Create a factory function** following the engine-first pattern
3. **Add DSL support** by ensuring the transpiler recognizes your factory function
4. **Document parameters** in the class hierarchy documentation
5. **Test with DSL** to ensure users can access your animation declaratively
**Remember**: Users should primarily interact with animations through the DSL. The programmatic API is mainly for framework development and advanced integrations.
This guide provides everything needed to create professional-quality animation classes that integrate seamlessly with the Berry Animation Framework's parameter system and rendering pipeline.

View File

@ -1,798 +0,0 @@
# DSL Reference - Berry Animation Framework
This document provides a comprehensive reference for the Animation DSL (Domain-Specific Language), which allows you to define animations using a declarative syntax with named parameters.
## Module Import
The DSL functionality is provided by a separate module:
```berry
import animation # Core framework (required)
import animation_dsl # DSL compiler and runtime (required for DSL)
```
## Why Use the DSL?
### Benefits
- **Declarative syntax**: Describe what you want, not how to implement it
- **Readable code**: Natural language-like syntax
- **Rapid prototyping**: Quick iteration on animation ideas
- **Event-driven**: Built-in support for interactive animations
- **Composition**: Easy layering and sequencing of animations
### When to Use DSL vs Programmatic
**Use DSL when:**
- Creating complex animation sequences
- Building interactive, event-driven animations
- Rapid prototyping and experimentation
- Non-programmers need to create animations
- You want declarative, readable animation definitions
**Use programmatic API when:**
- Building reusable animation components
- Performance is critical (DSL has compilation overhead)
- You need fine-grained control over animation logic
- Integrating with existing Berry code
- Firmware size is constrained (DSL module can be excluded)
## Transpiler Architecture
For detailed information about the DSL transpiler's internal architecture, including the core processing flow and expression processing chain, see [TRANSPILER_ARCHITECTURE.md](TRANSPILER_ARCHITECTURE.md).
## DSL API Functions
### Core Functions
#### `animation_dsl.compile(source)`
Compiles DSL source code to Berry code without executing it.
```berry
var dsl_source = "color red = 0xFF0000\n"
"animation red_anim = solid(color=red)\n"
"run red_anim"
var berry_code = animation_dsl.compile(dsl_source)
print(berry_code) # Shows generated Berry code
```
#### `animation_dsl.execute(source)`
Compiles and executes DSL source code in one step.
```berry
animation_dsl.execute("color blue = 0x0000FF\n"
"animation blue_anim = solid(color=blue)\n"
"run blue_anim for 5s")
```
#### `animation_dsl.load_file(filename)`
Loads DSL source from a file and executes it.
```berry
# Create a DSL file
var f = open("my_animation.dsl", "w")
f.write("color green = 0x00FF00\n"
"animation pulse_green = pulsating_animation(color=green, period=2s)\n"
"run pulse_green")
f.close()
# Load and execute
animation_dsl.load_file("my_animation.dsl")
```
## DSL Language Overview
The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility.
### Key Syntax Features
- **Import statements**: `import module_name` for loading Berry modules
- **Named parameters**: All function calls use `name=value` syntax
- **Time units**: `2s`, `500ms`, `1m`, `1h`
- **Hex colors**: `0xFF0000`, `0x80FF0000` (ARGB)
- **Named colors**: `red`, `blue`, `white`, etc.
- **Comments**: `# This is a comment`
- **Property assignment**: `animation.property = value`
- **User functions**: `function_name()` for custom functions
### Basic Structure
```berry
# Import statements (optional, for user functions or custom modules)
import user_functions
# Optional strip configuration
strip length 60
# Color definitions
color red = 0xFF0000
color blue = 0x0000FF
# Animation definitions with named parameters
animation pulse_red = pulsating_animation(color=red, period=2s)
animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
# Property assignments with user functions
pulse_red.priority = 10
pulse_red.opacity = breathing_effect()
comet_blue.direction = -1
# Execution
run pulse_red
```
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
## Symbol Resolution
The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups:
### Transpile-Time Symbol Resolution
When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities:
```berry
# If SINE exists in animation module
animation wave = wave_animation(waveform=SINE)
# Transpiles to: animation.SINE (direct access)
# If custom_color doesn't exist in animation module
color custom_color = 0xFF0000
animation solid_red = solid(color=custom_color)
# Transpiles to: custom_color_ (user-defined variable)
```
### Benefits
- **Performance**: Eliminates runtime symbol lookups for built-in constants
- **Error Detection**: Catches undefined symbols at compile time
- **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols
- **Optimization**: Direct access to animation module symbols is faster
### Symbol Categories
**Built-in Symbols** (resolved to `animation.<symbol>`):
- Animation factory functions: `solid`, `pulsating_animation`, `comet_animation`
- Value providers: `triangle`, `smooth`, `sine`, `static_value`
- Color providers: `color_cycle`, `breathe_color`, `rich_palette`
- Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc.
**User-defined Symbols** (resolved to `<symbol>_`):
- Custom colors: `my_red`, `fire_color`
- Custom animations: `pulse_effect`, `rainbow_wave`
- Variables: `brightness_level`, `cycle_time`
### Property Assignment Resolution
Property assignments also use the same resolution logic:
```berry
# Built-in symbol (if 'engine' existed in animation module)
engine.brightness = 200
# Would transpile to: animation.engine.brightness = 200
# User-defined symbol
my_animation.priority = 10
# Transpiles to: my_animation_.priority = 10
```
This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code.
## Import Statement Transpilation
The DSL supports importing Berry modules using the `import` keyword, which provides a clean way to load user functions and custom modules.
### Import Syntax
```berry
# DSL Import Syntax
import user_functions
import my_custom_module
import math
```
### Transpilation Behavior
Import statements are transpiled directly to Berry import statements with quoted module names:
```berry
# DSL Code
import user_functions
# Transpiles to Berry Code
import "user_functions"
```
### Import Processing
1. **Early Processing**: Import statements are processed early in transpilation
2. **Module Loading**: Imported modules are loaded using standard Berry import mechanism
3. **Function Registration**: User function modules should register functions using `animation.register_user_function()`
4. **No Validation**: The DSL doesn't validate module existence at compile time
### Example Import Workflow
**Step 1: Create User Functions Module (`user_functions.be`)**
```berry
import animation
def rand_demo(engine)
import math
return math.rand() % 256
end
# Register for DSL use
animation.register_user_function("rand_demo", rand_demo)
```
**Step 2: Use in DSL**
```berry
import user_functions
animation test = solid(color=blue)
test.opacity = rand_demo()
run test
```
**Step 3: Generated Berry Code**
```berry
import animation
var engine = animation.init_strip()
import "user_functions"
var test_ = animation.solid(engine)
test_.color = 0xFF0000FF
test_.opacity = animation.create_closure_value(engine,
def (engine) return animation.get_user_function('rand_demo')(engine) end)
engine.add(test_)
engine.run()
```
## Berry Code Block Transpilation
The DSL supports embedding arbitrary Berry code using the `berry` keyword with triple-quoted strings. This provides an escape hatch for complex logic while maintaining the declarative nature of the DSL.
### Berry Code Block Syntax
```berry
# DSL Berry Code Block
berry """
import math
var custom_value = math.pi * 2
print("Custom calculation:", custom_value)
"""
```
### Transpilation Behavior
Berry code blocks are copied verbatim to the generated Berry code with comment markers:
```berry
# DSL Code
berry """
var test_var = 42
print("Hello from berry block")
"""
# Transpiles to Berry Code
# Berry code block
var test_var = 42
print("Hello from berry block")
# End berry code block
```
### Integration with DSL Objects
Berry code can interact with DSL-generated objects by using the underscore suffix naming convention:
```berry
# DSL Code
animation pulse = pulsating_animation(color=red, period=2s)
berry """
pulse_.opacity = 200
pulse_.priority = 10
"""
# Transpiles to Berry Code
var pulse_ = animation.pulsating_animation(engine)
pulse_.color = animation.red
pulse_.period = 2000
# Berry code block
pulse_.opacity = 200
pulse_.priority = 10
# End berry code block
```
## Advanced DSL Features
### Templates
The DSL supports two types of templates: regular templates (functions) and template animations (classes).
#### Template Animation Transpilation
Template animations create reusable animation classes extending `engine_proxy`:
```berry
# DSL Template Animation
template animation shutter_effect {
param colors type palette nillable true
param duration type time min 0 max 3600 default 5 nillable false
set strip_len = strip_length()
color col = color_cycle(colors=colors, period=0)
animation shutter = beacon_animation(
color = col
beacon_size = strip_len / 2
)
sequence seq repeat forever {
play shutter for duration
col.next = 1
}
run seq
}
```
**Transpiles to:**
```berry
class shutter_effect_animation : animation.engine_proxy
static var PARAMS = animation.enc_params({
"colors": {"type": "palette", "nillable": true},
"duration": {"type": "time", "min": 0, "max": 3600, "default": 5, "nillable": false}
})
def init(engine)
super(self).init(engine)
var strip_len_ = animation.strip_length(engine)
var col_ = animation.color_cycle(engine)
col_.colors = animation.create_closure_value(engine, def (engine) return self.colors end)
col_.period = 0
var shutter_ = animation.beacon_animation(engine)
shutter_.color = col_
shutter_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 2 end)
var seq_ = animation.sequence_manager(engine, -1)
.push_play_step(shutter_, animation.resolve(self.duration))
.push_closure_step(def (engine) col_.next = 1 end)
self.add(seq_)
end
end
```
**Key Features:**
- Parameters accessed as `self.<param>` and wrapped in closures
- Constraints (min, max, default, nillable) encoded in PARAMS
- Uses `self.add()` instead of `engine.add()`
- Can be instantiated multiple times with different parameters
#### Regular Template Transpilation
Regular templates generate Berry functions:
```berry
# DSL Template
template pulse_effect {
param color type color
param speed
animation pulse = pulsating_animation(color=color, period=speed)
run pulse
}
```
**Transpiles to:**
```berry
def pulse_effect_template(engine, color_, speed_)
var pulse_ = animation.pulsating_animation(engine)
pulse_.color = color_
pulse_.period = speed_
engine.add(pulse_)
end
animation.register_user_function('pulse_effect', pulse_effect_template)
```
#### Template vs Template Animation
**Template Animation** (`template animation`):
- Generates classes extending `engine_proxy`
- Parameters accessed as `self.<param>`
- Supports parameter constraints (min, max, default, nillable)
- Uses `self.add()` for composition
- Can be instantiated multiple times
**Regular Template** (`template`):
- Generates functions
- Parameters accessed as `<param>_`
- Uses `engine.add()` for execution
- Called like functions
### User-Defined Functions
Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments:
```berry
# Define custom function in Berry - engine must be first parameter
def custom_twinkle(engine, color, count, period)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.count = count
atml:parameter>
</invoke>
return anim
end
# Register the function for DSL use
animation.register_user_function("twinkle", custom_twinkle)
```
```berry
# Use in DSL - engine is automatically passed as first argument
animation gold_twinkle = twinkle(0xFFD700, 8, 500ms)
animation blue_twinkle = twinkle(blue, 12, 300ms)
run gold_twinkle
```
**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function.
For comprehensive examples and best practices, see the **[User Functions Guide](USER_FUNCTIONS.md)**.
### Event System
Define event handlers that respond to triggers:
```berry
# Define animations for different states
color normal = 0x000080
color alert = 0xFF0000
animation normal_state = solid(color=normal)
animation alert_state = pulsating_animation(color=alert, period=500ms)
# Event handlers
on button_press {
run alert_state for 3s
run normal_state
}
on sensor_trigger {
run alert_state for 5s
wait 1s
run normal_state
}
# Default state
run normal_state
```
### Nested Function Calls
DSL supports nested function calls for complex compositions:
```berry
# Nested calls in animation definitions (now supported)
animation complex = pulsating_animation(
color=red,
period=2s
)
# Nested calls in run statements
sequence demo {
play pulsating_animation(color=blue, period=1s) for 10s
}
```
## Error Handling
The DSL compiler validates classes and parameters at transpilation time, catching errors before execution:
```berry
var invalid_dsl = "color red = #INVALID_COLOR\n"
"animation bad = unknown_function(red)\n"
"animation pulse = pulsating_animation(invalid_param=123)"
try
animation_dsl.execute(invalid_dsl)
except .. as e
print("DSL Error:", e)
end
```
### Transpilation-Time Validation
The DSL performs comprehensive validation during compilation:
**Animation Factory Validation:**
```berry
# Error: Function doesn't exist
animation bad = nonexistent_animation(color=red)
# Transpiler error: "Animation factory function 'nonexistent_animation' does not exist"
# Error: Function exists but doesn't create animation
animation bad2 = math_function(value=10)
# Transpiler error: "Function 'math_function' does not create an animation instance"
```
**Parameter Validation:**
```berry
# Error: Invalid parameter name in constructor
animation pulse = pulsating_animation(invalid_param=123)
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
# Error: Invalid parameter name in property assignment
animation pulse = pulsating_animation(color=red, period=2s)
pulse.wrong_arg = 15
# Transpiler error: "Animation 'PulseAnimation' does not have parameter 'wrong_arg'"
# Error: Parameter constraint violation
animation comet = comet_animation(tail_length=-5)
# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1"
```
**Color Provider Validation:**
```berry
# Error: Color provider doesn't exist
color bad = nonexistent_color_provider(period=2s)
# Transpiler error: "Color provider factory 'nonexistent_color_provider' does not exist"
# Error: Function exists but doesn't create color provider
color bad2 = pulsating_animation(color=red)
# Transpiler error: "Function 'pulsating_animation' does not create a color provider instance"
```
**Reference Validation:**
```berry
# Error: Undefined color reference
animation pulse = pulsating_animation(color=undefined_color)
# Transpiler error: "Undefined reference: 'undefined_color'"
# Error: Undefined animation reference in run statement
run nonexistent_animation
# Transpiler error: "Undefined reference 'nonexistent_animation' in run"
# Error: Undefined animation reference in sequence
sequence demo {
play nonexistent_animation for 5s
}
# Transpiler error: "Undefined reference 'nonexistent_animation' in sequence play"
```
**Function Call Safety Validation:**
```berry
# Error: Dangerous function creation in computed expression
set strip_len3 = (strip_length() + 1) / 2
# Transpiler error: "Function 'strip_length()' cannot be used in computed expressions.
# This creates a new instance at each evaluation. Use either:
# set var_name = strip_length() # Single function call
# set computed = (existing_var + 1) / 2 # Computation with existing values"
```
**Why This Validation Exists:**
The transpiler prevents dangerous patterns where functions that create instances are called inside computed expressions that get wrapped in closures. This would create a new instance every time the closure is evaluated, leading to:
- Memory leaks
- Performance degradation
- Inconsistent behavior due to multiple timing states
**Safe Alternative:**
```berry
# ✅ CORRECT: Separate function call from computation
set strip_len = strip_length() # Single function call
set strip_len3 = (strip_len + 1) / 2 # Computation with existing value
```
**Template Parameter Validation:**
```berry
# Error: Duplicate parameter names
template bad_template {
param color type color
param color type number # Error: duplicate parameter name
}
# Transpiler error: "Duplicate parameter name 'color' in template"
# Error: Reserved keyword as parameter name
template reserved_template {
param animation type color # Error: conflicts with reserved keyword
}
# Transpiler error: "Parameter name 'animation' conflicts with reserved keyword"
# Error: Built-in color name as parameter
template color_template {
param red type number # Error: conflicts with built-in color
}
# Transpiler error: "Parameter name 'red' conflicts with built-in color name"
# Error: Invalid type annotation
template type_template {
param value type invalid_type # Error: invalid type
}
# Transpiler error: "Invalid parameter type 'invalid_type'. Valid types are: [...]"
# Warning: Unused parameter (compilation succeeds)
template unused_template {
param used_color type color
param unused_param type number # Warning: never used
animation test = solid(color=used_color)
run test
}
# Transpiler warning: "Template 'unused_template' parameter 'unused_param' is declared but never used"
```
### Error Categories
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
- **Factory validation**: Non-existent or invalid animation/color provider factories
- **Parameter validation**: Invalid parameter names in constructors or property assignments
- **Template validation**: Invalid template parameter names, types, or usage patterns
- **Constraint validation**: Parameter values that violate defined constraints (min/max, enums, types)
- **Reference validation**: Using undefined colors, animations, or variables
- **Type validation**: Incorrect parameter types or incompatible assignments
- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
### Warning Categories
The DSL transpiler also generates **warnings** that don't prevent compilation but indicate potential code quality issues:
- **Unused parameters**: Template parameters that are declared but never used in the template body
- **Code quality**: Suggestions for better coding practices
**Warning Behavior:**
- Warnings are included as comments in the generated Berry code
- Compilation succeeds even with warnings present
- Warnings help maintain code quality without being overly restrictive
## Performance Considerations
### DSL vs Programmatic Performance
- **DSL compilation overhead**: ~10-50ms depending on complexity
- **Generated code performance**: Identical to hand-written Berry code
- **Memory usage**: DSL compiler uses temporary memory during compilation
### Optimization Tips
1. **Compile once, run many times**:
```berry
var compiled = animation_dsl.compile(dsl_source)
var fn = compile(compiled)
# Run multiple times without recompilation
fn() # First execution
fn() # Subsequent executions are faster
```
2. **Use programmatic API for performance-critical code**:
```berry
# DSL for high-level structure
animation_dsl.execute(
"sequence main {\n"
"play performance_critical_anim for 10s\n"
"}\n"
"run main"
)
# Programmatic for performance-critical animations
var performance_critical_anim = animation.create_optimized_animation()
```
## Integration Examples
### With Tasmota Rules
```berry
# In autoexec.be
import animation
import animation_dsl
def handle_rule_trigger(event)
if event == "motion"
animation_dsl.execute("color alert = 0xFF0000\n"
"animation alert_anim = pulsating_animation(color=alert, period=500ms)\n"
"run alert_anim for 5s")
elif event == "door"
animation_dsl.execute("color welcome = 0x00FF00\n"
"animation welcome_anim = breathe_animation(color=welcome, period=2s)\n"
"run welcome_anim for 8s")
end
end
# Register with Tasmota's rule system
tasmota.add_rule("motion", handle_rule_trigger)
```
### With Web Interface
```berry
# Create web endpoints for DSL execution
import webserver
def web_execute_dsl()
var dsl_code = webserver.arg("dsl")
if dsl_code
try
animation_dsl.execute(dsl_code)
webserver.content_response("DSL executed successfully")
except .. as e
webserver.content_response(f"DSL Error: {e}")
end
else
webserver.content_response("No DSL code provided")
end
end
webserver.on("/execute_dsl", web_execute_dsl)
```
## Best Practices
1. **Structure your DSL files**:
```berry
# Strip configuration first
strip length 60
# Colors next
color red = 0xFF0000
color blue = 0x0000FF
# Animations with named parameters
animation red_solid = solid(color=red)
animation pulse_red = pulsating_animation(color=red, period=2s)
# Property assignments
pulse_red.priority = 10
# Sequences
sequence demo {
play pulse_red for 5s
}
# Execution last
run demo
```
2. **Use meaningful names**:
```berry
# Good
color warning_red = 0xFF0000
animation door_alert = pulsating_animation(color=warning_red, period=500ms)
# Avoid
color c1 = 0xFF0000
animation a1 = pulsating_animation(color=c1, period=500ms)
```
3. **Comment your DSL**:
```berry
# Security system colors
color normal_blue = 0x000080 # Idle state
color alert_red = 0xFF0000 # Alert state
color success_green = 0x00FF00 # Success state
# Main security animation sequence
sequence security_demo {
play solid(color=normal_blue) for 10s # Normal operation
play pulsating_animation(color=alert_red, period=500ms) for 3s # Alert
play breathe_animation(color=success_green, period=2s) for 5s # Success confirmation
}
```
4. **Organize complex projects**:
```berry
# Load DSL modules
animation_dsl.load_file("colors.dsl") # Color definitions
animation_dsl.load_file("animations.dsl") # Animation library
animation_dsl.load_file("sequences.dsl") # Sequence definitions
animation_dsl.load_file("main.dsl") # Main execution
```
This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed.

View File

@ -1,479 +0,0 @@
# Examples
Essential examples showcasing the Tasmota Berry Animation Framework using DSL syntax.
## Basic Animations
### 1. Solid Color
```berry
color red = 0xFF0000
animation red_solid = solid(color=red)
run red_solid
```
### 2. Pulsing Effect
```berry
color blue = 0x0000FF
animation blue_pulse = pulsating_animation(color=blue, period=2s)
run blue_pulse
```
### 3. Moving Comet
```berry
color cyan = 0x00FFFF
animation comet_trail = comet_animation(color=cyan, tail_length=8, speed=100ms, direction=1)
run comet_trail
```
## Using Value Providers
### 4. Breathing Effect
```berry
set breathing = smooth(min_value=50, max_value=255, period=3s)
color white = 0xFFFFFF
animation breathing_white = solid(color=white)
breathing_white.opacity = breathing
run breathing_white
```
### 5. Color Cycling
```berry
color rainbow = rainbow_color_provider(period=5s)
animation rainbow_cycle = solid(color=rainbow)
run rainbow_cycle
```
## Palette Animations
### 6. Fire Effect
```berry
palette fire_colors = [
(0, 0x000000), # Black
(128, 0xFF0000), # Red
(192, 0xFF8000), # Orange
(255, 0xFFFF00) # Yellow
]
animation fire_effect = palette_animation(colors=fire_colors, period=2s, intensity=255)
run fire_effect
```
## Sequences
### 7. RGB Show
```berry
color red = 0xFF0000
color green = 0x00FF00
color blue = 0x0000FF
animation red_anim = solid(color=red)
animation green_anim = solid(color=green)
animation blue_anim = solid(color=blue)
sequence rgb_show {
play red_anim for 2s
play green_anim for 2s
play blue_anim for 2s
}
run rgb_show
```
### 8. Sunrise Sequence
```berry
color deep_blue = 0x000080
color orange = 0xFFA500
color yellow = 0xFFFF00
animation night = solid(color=deep_blue)
animation sunrise = pulsating_animation(color=orange, period=3s)
animation day = solid(color=yellow)
sequence sunrise_show {
log("Starting sunrise sequence")
play night for 3s
log("Night phase complete, starting sunrise")
play sunrise for 5s
log("Sunrise complete, switching to day")
play day for 3s
log("Sunrise sequence finished")
}
run sunrise_show
```
### 8.1. Variable Duration Sequences
```berry
# Define timing variables for consistent durations
set short_duration = 2s
set long_duration = 5s
set fade_time = 1s
animation red_anim = solid(color=red)
animation green_anim = solid(color=green)
animation blue_anim = solid(color=blue)
sequence timed_show forever {
play red_anim for short_duration # Use variable duration
wait fade_time # Variable wait time
play green_anim for long_duration # Different variable duration
wait fade_time
play blue_anim for short_duration # Reuse timing variable
}
run timed_show
```
## Sequence Assignments
### 9. Dynamic Property Changes
```berry
# Create oscillators for dynamic position
set triangle_val = triangle(min_value=0, max_value=27, duration=5s)
set cosine_val = cosine_osc(min_value=0, max_value=27, duration=5s)
# Create color cycle
palette eye_palette = [red, yellow, green, violet]
color eye_color = color_cycle(colors=eye_palette, period=0)
# Create beacon animation
animation red_eye = beacon_animation(
color=eye_color
pos=cosine_val
beacon_size=3
slew_size=2
priority=10
)
# Sequence with property assignments
sequence cylon_eye {
play red_eye for 3s
red_eye.pos = triangle_val # Change to triangle oscillator
play red_eye for 3s
red_eye.pos = cosine_val # Change back to cosine
eye_color.next = 1 # Advance to next color
}
run cylon_eye
```
### 10. Multiple Assignments in Sequence
```berry
set high_brightness = 255
set low_brightness = 64
color my_blue = 0x0000FF
animation test = solid(color=red)
test.opacity = high_brightness
sequence demo {
play test for 1s
test.opacity = low_brightness # Dim the animation
test.color = my_blue # Change color to blue
play test for 1s
test.opacity = high_brightness # Brighten again
play test for 1s
}
run demo
```
### 11. Restart in Sequences
```berry
# Create oscillator and animation
set wave_osc = triangle(min_value=0, max_value=29, period=4s)
animation wave = beacon_animation(color=blue, pos=wave_osc, beacon_size=5)
sequence sync_demo {
play wave for 3s
restart wave_osc # Restart oscillator time origin (if already started)
play wave for 3s # Wave starts from beginning again
restart wave # Restart animation time origin (if already started)
play wave for 3s
}
run sync_demo
```
### 12. Assignments in Repeat Blocks
```berry
set brightness = smooth(min_value=50, max_value=255, period=2s)
animation pulse = pulsating_animation(color=white, period=1s)
sequence breathing_cycle {
repeat 3 times {
play pulse for 500ms
pulse.opacity = brightness # Apply breathing effect
wait 200ms
pulse.opacity = 255 # Return to full brightness
}
}
run breathing_cycle
```
## User Functions in Computed Parameters
### 13. Simple User Function
```berry
# Simple user function in computed parameter
animation random_base = solid(color=blue, priority=10)
random_base.opacity = rand_demo()
run random_base
```
### 14. User Function with Math Operations
```berry
# Mix user functions with mathematical functions
animation random_bounded = solid(
color=purple
opacity=max(50, min(255, rand_demo() + 100))
priority=15
)
run random_bounded
```
### 15. User Function in Arithmetic Expression
```berry
# Use user function in arithmetic expressions
animation random_variation = solid(
color=cyan
opacity=abs(rand_demo() - 128) + 64
priority=12
)
run random_variation
```
See `anim_examples/user_functions_demo.anim` for a complete working example.
## New Repeat System Examples
### 16. Runtime Repeat with Forever Loop
```berry
color red = 0xFF0000
color blue = 0x0000FF
animation red_anim = solid(color=red)
animation blue_anim = solid(color=blue)
# Traditional syntax with repeat sub-sequence
sequence cylon_effect {
repeat forever {
play red_anim for 1s
play blue_anim for 1s
}
}
# Alternative syntax - sequence with repeat modifier
sequence cylon_effect_alt repeat forever {
play red_anim for 1s
play blue_anim for 1s
}
run cylon_effect
```
### 17. Nested Repeats (Multiplication)
```berry
color green = 0x00FF00
color yellow = 0xFFFF00
animation green_anim = solid(color=green)
animation yellow_anim = solid(color=yellow)
# Nested repeats: 3 × 2 = 6 total iterations
sequence nested_pattern {
repeat 3 times {
repeat 2 times {
play green_anim for 200ms
play yellow_anim for 200ms
}
wait 500ms # Pause between outer iterations
}
}
run nested_pattern
```
### 18. Repeat with Property Assignments
```berry
set triangle_pos = triangle(min_value=0, max_value=29, period=3s)
set cosine_pos = cosine_osc(min_value=0, max_value=29, period=3s)
color eye_color = color_cycle(colors=[red, yellow, green, blue], period=0)
animation moving_eye = beacon_animation(
color=eye_color
pos=triangle_pos
beacon_size=2
slew_size=1
)
sequence dynamic_cylon {
repeat 5 times {
play moving_eye for 2s
moving_eye.pos = cosine_pos # Switch to cosine movement
play moving_eye for 2s
moving_eye.pos = triangle_pos # Switch back to triangle
eye_color.next = 1 # Next color
}
}
run dynamic_cylon
```
## Advanced Examples
### 19. Dynamic Position
```berry
strip length 60
set moving_position = smooth(min_value=5, max_value=55, period=4s)
color purple = 0x8000FF
animation moving_pulse = beacon_animation(
color=purple,
position=moving_position,
beacon_size=3,
fade_size=2
)
run moving_pulse
```
### 20. Multi-Layer Effect
```berry
# Base layer - slow breathing
set breathing = smooth(min_value=100, max_value=255, period=4s)
color base_blue = 0x000080
animation base_layer = solid(color=base_blue)
base_layer.opacity = breathing
# Accent layer - twinkling stars
color star_white = 0xFFFFFF
animation stars = twinkle_animation(color=star_white, count=5, period=800ms)
stars.opacity = 150
sequence layered_effect {
play base_layer for 10s
play stars for 10s
}
run layered_effect
```
## Tips for Creating Animations
### Start Simple
```berry
# Begin with basic colors and effects
color my_color = 0xFF0000
animation simple = solid(color=my_color)
run simple
```
### Use Meaningful Names
```berry
# Good - descriptive names
color sunset_orange = 0xFF8C00
animation evening_glow = pulsating_animation(color=sunset_orange, period=4s)
# Avoid - unclear names
color c1 = 0xFF8C00
animation a1 = pulsating_animation(color=c1, period=4s)
```
### Test Incrementally
1. Start with solid colors
2. Add simple effects like pulse
3. Experiment with sequences
4. Combine multiple animations
### Performance Considerations
- Use sequences instead of multiple simultaneous animations
- Reuse value providers with the `set` keyword
- Keep animation periods reasonable (>500ms)
- Limit palette sizes for memory efficiency
## Template Examples
Templates provide reusable, parameterized animation patterns that promote code reuse and maintainability.
### 21. Simple Template
```berry
# Define a reusable blinking template
template blink_effect {
param color type color
param speed
param intensity
animation blink = pulsating_animation(
color=color
period=speed
)
blink.opacity = intensity
run blink
}
# Use the template with different parameters
blink_effect(red, 1s, 80%)
blink_effect(blue, 500ms, 100%)
```
### 22. Multi-Animation Template
```berry
# Template that creates a comet chase effect
template comet_chase {
param trail_color type color
param bg_color type color
param chase_speed
param tail_size
# Background layer
animation background = solid(color=bg_color)
background.priority = 1
# Comet effect layer
animation comet = comet_animation(
color=trail_color
tail_length=tail_size
speed=chase_speed
)
comet.priority = 10
run background
run comet
}
# Create different comet effects
comet_chase(white, black, 1500ms, 8)
```
### 23. Template with Dynamic Colors
```berry
# Template using color cycling and breathing effects
template breathing_rainbow {
param cycle_time
param breath_time
param base_brightness
# Create rainbow palette
colors rainbow = [
(0, red), (42, orange), (85, yellow)
(128, green), (170, blue), (213, purple), (255, red)
]
# Create cycling rainbow color
color rainbow_cycle = color_cycle(
colors=rainbow
period=cycle_time
)
# Create breathing animation with rainbow colors
animation breath = pulsating_animation(
color=rainbow_cycle
period=breath_time
)
breath.opacity = base_brightness
run breath
}
# Use the rainbow breathing template
breathing_rainbow(5s, 2s, 200)
```
## Next Steps
- **[DSL Reference](DSL_REFERENCE.md)** - Complete language syntax
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
- **[Animation Development](ANIMATION_DEVELOPMENT.md)** - Creating custom animations
Start with these examples and build your own amazing LED animations! 🎨✨

View File

@ -1,263 +0,0 @@
# Oscillation Patterns
Quick reference for oscillation patterns used with value providers in the Berry Animation Framework.
## Available Oscillation Patterns
These waveform constants can be used with `oscillator_value`:
| Constant | Value | Alias Functions | Behavior | Use Case |
|----------|-------|-----------------|----------|----------|
| `SAWTOOTH` | 1 | `linear`, `ramp` | Linear ramp up | Uniform motion |
| `TRIANGLE` | 2 | `triangle` | Linear up then down | Sharp direction changes |
| `SQUARE` | 3 | `square` | Alternating min/max | On/off effects |
| `COSINE` | 4 | `smooth` | Smooth cosine wave | Natural oscillation |
| `SINE` | 5 | `sine` | Pure sine wave | Classic wave motion |
| `EASE_IN` | 6 | `ease_in` | Slow start, fast end | Smooth acceleration |
| `EASE_OUT` | 7 | `ease_out` | Fast start, slow end | Smooth deceleration |
| `ELASTIC` | 8 | `elastic` | Spring overshoot | Bouncy effects |
| `BOUNCE` | 9 | `bounce` | Ball bouncing | Physics simulation |
## DSL Usage
### With Oscillator Value Provider
```berry
# Basic oscillator with different waveform types
set breathing = oscillator_value(min_value=50, max_value=255, duration=3000, form=COSINE)
set pulsing = ease_in(min_value=0, max_value=255, duration=2000)
set bouncing = oscillator_value(min_value=10, max_value=240, duration=4000, form=TRIANGLE)
```
### Using Alias Functions
```berry
# These are equivalent to oscillator_value with specific forms
set smooth_fade = smooth(min_value=50, max_value=255, duration=3000) # form=COSINE
set sine_wave = sine_osc(min_value=50, max_value=255, duration=3000) # form=SINE
set cosine_wave = cosine_osc(min_value=50, max_value=255, duration=3000) # form=COSINE (alias for smooth)
set linear_sweep = linear(min_value=0, max_value=255, duration=2000) # form=SAWTOOTH
set triangle_wave = triangle(min_value=10, max_value=240, duration=4000) # form=TRIANGLE
```
### In Animations
```berry
color blue = 0x0000FF
set breathing = smooth(min_value=100, max_value=255, duration=4000)
animation breathing_blue = solid(color=blue)
breathing_blue.opacity = breathing
run breathing_blue
```
## Pattern Characteristics
### SAWTOOTH (Linear)
- **Constant speed** throughout the cycle
- **Sharp reset** from max back to min
- **Best for**: Uniform sweeps, mechanical movements
```
Value
^
| /| /|
| / | / |
| / | / |
| / | / |
| / | / |
|/ |/ |
+------+------+----> Time
```
```berry
set linear_brightness = linear(min_value=0, max_value=255, duration=2000)
```
### COSINE (Smooth)
- **Gradual acceleration** and deceleration
- **Natural feeling** transitions
- **Best for**: Breathing effects, gentle fades
```berry
set breathing_effect = smooth(min_value=50, max_value=255, duration=3000)
```
### SINE (Pure Wave)
- **Classic sine wave** starting from minimum
- **Smooth acceleration** and deceleration like cosine but phase-shifted
- **Best for**: Wave effects, classic oscillations, audio-visual sync
```
Value
^
| ___
| / \
| / \
| / \
| / \
| / \
| / \
| / \
|/ \___
+--------------------+----> Time
```
```berry
set wave_motion = sine_osc(min_value=0, max_value=255, duration=2000)
```
### TRIANGLE
- **Linear acceleration** to midpoint, then **linear deceleration**
- **Sharp direction changes** at extremes
- **Best for**: Bouncing effects, sharp transitions
```
Value
^
| /\
| / \
| / \
| / \
| / \
| / \
|/ \
+-------------+----> Time
```
```berry
set bounce_position = triangle(min_value=5, max_value=55, duration=2000)
```
### SQUARE
- **Alternating** between min and max values
- **Instant transitions** with configurable duty cycle
- **Best for**: On/off effects, strobing, digital patterns
```
Value
^
| +---+ +---+
| | | | |
| | | | |
| | +-----+ |
| | |
| | |
+-+-------------+----> Time
```
```berry
set strobe_effect = square(min_value=0, max_value=255, duration=500, duty_cycle=25)
```
### EASE_IN
- **Slow start**, **fast finish**
- **Smooth acceleration** curve
- **Best for**: Starting animations, building intensity
```berry
set accelerating = ease_in(min_value=0, max_value=255, duration=3000)
```
### EASE_OUT
- **Fast start**, **slow finish**
- **Smooth deceleration** curve
- **Best for**: Ending animations, gentle stops
```berry
set decelerating = ease_out(min_value=255, max_value=0, duration=3000)
```
## Value Progression Examples
For a cycle from 0 to 100 over 2000ms:
| Time | SAWTOOTH | COSINE | SINE | TRIANGLE | EASE_IN | EASE_OUT |
|------|----------|--------|------|----------|---------|----------|
| 0ms | 0 | 0 | 0 | 0 | 0 | 0 |
| 500ms| 25 | 15 | 50 | 50 | 6 | 44 |
| 1000ms| 50 | 50 | 100 | 100 | 25 | 75 |
| 1500ms| 75 | 85 | 50 | 50 | 56 | 94 |
| 2000ms| 100 | 100 | 0 | 0 | 100 | 100 |
## Common Patterns
### Breathing Effect
```berry
color soft_white = 0xC0C0C0
set breathing = smooth(min_value=80, max_value=255, duration=4000)
animation breathing_light = solid(color=soft_white)
breathing_light.opacity = breathing
run breathing_light
```
### Position Sweep
```berry
strip length 60
color red = 0xFF0000
set sweeping_position = linear(min_value=0, max_value=59, duration=3000)
animation position_sweep = beacon_animation(
color=red,
position=sweeping_position,
beacon_size=3,
fade_size=1
)
run position_sweep
```
### Wave Motion
```berry
color purple = 0x8000FF
set wave_brightness = sine(min_value=50, max_value=255, duration=2500)
animation wave_effect = solid(color=purple)
wave_effect.opacity = wave_brightness
run wave_effect
```
### Bouncing Effect
```berry
color green = 0x00FF00
set bounce_size = triangle(min_value=1, max_value=8, duration=1000)
animation bouncing_pulse = beacon_animation(
color=green,
position=30,
beacon_size=bounce_size,
fade_size=1
)
run bouncing_pulse
```
### Accelerating Fade
```berry
color blue = 0x0000FF
set fade_in = ease_in(min_value=0, max_value=255, duration=5000)
animation accelerating_fade = solid(color=blue)
accelerating_fade.opacity = fade_in
run accelerating_fade
```
### Strobe Effect
```berry
color white = 0xFFFFFF
set strobe_pattern = square(min_value=0, max_value=255, duration=200, duty_cycle=10)
animation strobe_light = solid(color=white)
strobe_light.opacity = strobe_pattern
run strobe_light
```
## Tips
- **COSINE (smooth)**: Most natural for breathing and gentle effects
- **SINE**: Classic wave motion, perfect for audio-visual sync and pure oscillations
- **SAWTOOTH (linear)**: Best for consistent sweeps and mechanical movements
- **TRIANGLE**: Creates sharp, bouncing transitions
- **EASE_IN**: Perfect for building up intensity
- **EASE_OUT**: Ideal for gentle fade-outs
- **ELASTIC**: Spring-like effects with overshoot
- **BOUNCE**: Physics-based bouncing effects
- **SQUARE**: Good for on/off blinking effects
Choose the oscillation pattern that matches the feeling you want to create in your animation.

View File

@ -1,257 +0,0 @@
# Quick Start Guide
Get up and running with the Berry Animation Framework in 5 minutes using the DSL!
## Prerequisites
- Tasmota device with Berry support
- Addressable LED strip (WS2812, SK6812, etc.)
## Step 1: Your First Animation
Create a simple pulsing red light:
```berry
# Define colors
color bordeaux = 0x6F2C4F
# Create pulsing animation
animation pulse_bordeaux = pulsating_animation(color=bordeaux, period=3s)
# Run it
run pulse_bordeaux
```
## Step 2: Color Cycling
Create smooth color transitions:
```berry
# Use predefined rainbow palette
animation rainbow_cycle = rich_palette(
colors=PALETTE_RAINBOW
period=5s
transition_type=1
)
run rainbow_cycle
```
## Step 3: Custom Palettes
Create your own color palettes:
```berry
# Define a sunset palette
palette sunset = [
(0, 0x191970) # Midnight blue
(64, purple) # Purple
(128, 0xFF69B4) # Hot pink
(192, orange) # Orange
(255, yellow) # Yellow
]
# Create palette animation
animation sunset_glow = rich_palette(
colors=sunset
period=8s
transition_type=1
)
run sunset_glow
```
## Step 4: Sequences
Create complex shows with sequences:
```berry
animation red_pulse = pulsating_animation(color=red, period=2s)
animation green_pulse = pulsating_animation(color=green, period=2s)
animation blue_pulse = pulsating_animation(color=blue, period=2s)
sequence rgb_show {
play red_pulse for 3s
wait 500ms
play green_pulse for 3s
wait 500ms
play blue_pulse for 3s
repeat 2 times {
play red_pulse for 1s
play green_pulse for 1s
play blue_pulse for 1s
}
}
run rgb_show
```
**Pro Tip: Variable Durations**
Use variables for consistent timing:
```berry
# Define timing variables
set short_time = 1s
set long_time = 3s
sequence timed_show {
play red_pulse for long_time # Use variable duration
wait 500ms
play green_pulse for short_time # Different timing
play blue_pulse for long_time # Reuse timing
}
```
## Step 5: Dynamic Effects
Add movement and variation to your animations:
```berry
# Breathing effect with smooth oscillation
animation breathing = pulsating_animation(
color=blue
min_brightness=20%
max_brightness=100%
period=4s
)
# Moving comet effect
animation comet = comet_animation(
color=white
tail_length=8
speed=2000
)
# Twinkling effect
animation sparkles = twinkle_animation(
color=white
count=8
period=800ms
)
run breathing
```
## Common Patterns
### Fire Effect
```berry
animation fire = rich_palette(
colors=PALETTE_FIRE
period=2s
transition_type=1
)
run fire
```
## Loading DSL Files
Save your DSL code in `.anim` files and load them:
```berry
import animation
# Load DSL file
var runtime = animation.load_dsl_file("my_animation.anim")
```
## Templates - Reusable Animation Patterns
### Template Animations
Template animations create reusable animation classes with parameters:
```berry
# Define a template animation with constraints
template animation shutter_effect {
param colors type palette nillable true
param duration type time min 0 max 3600 default 5 nillable false
set strip_len = strip_length()
color col = color_cycle(colors=colors, period=0)
animation shutter = beacon_animation(
color = col
beacon_size = strip_len / 2
)
sequence seq repeat forever {
play shutter for duration
col.next = 1
}
run seq
}
# Create multiple instances with different parameters
palette rainbow = [red, orange, yellow, green, blue]
animation shutter1 = shutter_effect(colors=rainbow, duration=2s)
animation shutter2 = shutter_effect(colors=rainbow, duration=5s)
run shutter1
run shutter2
```
**Template Animation Features:**
- **Reusable Classes** - Create multiple instances with different parameters
- **Parameter Constraints** - min, max, default, nillable values
- **Composition** - Combine multiple animations and sequences
- **Type Safe** - Parameter type checking
- **Implicit Parameters** - Automatically inherit parameters from base classes (name, priority, duration, loop, opacity, color, is_running)
### Regular Templates
Regular templates generate functions for simpler use cases:
```berry
template pulse_effect {
param color type color
param speed
animation pulse = pulsating_animation(color=color, period=speed)
run pulse
}
# Use the template
pulse_effect(red, 2s)
pulse_effect(blue, 1s)
```
## User-Defined Functions (Advanced)
For complex logic, create custom functions in Berry:
```berry
# Define custom function - engine must be first parameter
def my_twinkle(engine, color, count, period)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.count = count
anim.period = period
return anim
end
# Register for DSL use
animation.register_user_function("twinkle", my_twinkle)
```
```berry
# Use in DSL - engine is automatically passed
animation gold_twinkles = twinkle(0xFFD700, 8, 500ms)
run gold_twinkles
```
**Note**: The DSL automatically passes `engine` as the first argument to user functions.
## Next Steps
- **[DSL Reference](DSL_REFERENCE.md)** - Complete DSL syntax and features
- **[User Functions](USER_FUNCTIONS.md)** - Create custom animation functions
- **[Examples](EXAMPLES.md)** - More complex animation examples
- **[Animation Class Hierarchy](ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
- **[Oscillation Patterns](OSCILLATION_PATTERNS.md)** - Dynamic value patterns
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
Happy animating! 🎨✨

View File

@ -1,858 +0,0 @@
# DSL Transpiler Architecture
This document provides a detailed overview of the Berry Animation DSL transpiler architecture, including the core processing flow and expression processing chain.
## Overview
The DSL transpiler (`transpiler.be`) converts Animation DSL code into executable Berry code. It uses a **ultra-simplified single-pass architecture** with comprehensive validation and code generation capabilities. The refactored transpiler emphasizes simplicity, robustness, and maintainability while providing extensive compile-time validation.
### Single-Pass Architecture Clarification
The transpiler is truly **single-pass** - it processes the token stream once from start to finish. When the documentation mentions "sequential steps" (like in template processing), these refer to **sequential operations within the single pass**, not separate passes over the data. For example:
- Template processing collects parameters, then collects body tokens **sequentially** in one pass
- Expression transformation handles mathematical functions, then user variables **sequentially** in one operation
- The transpiler never backtracks or re-processes the same tokens multiple times
## Core Processing Flow
The transpiler follows an **ultra-simplified single-pass architecture** with the following main flow:
```
transpile()
├── add("import animation")
├── while !at_end()
│ └── process_statement()
│ ├── Handle comments (preserve in output)
│ ├── Skip whitespace/newlines
│ ├── Auto-initialize strip if needed
│ ├── process_color()
│ │ ├── validate_user_name()
│ │ ├── _validate_color_provider_factory_exists()
│ │ └── _process_named_arguments_for_color_provider()
│ ├── process_palette()
│ │ ├── validate_user_name()
│ │ ├── Detect tuple vs alternative syntax
│ │ └── process_palette_color() (strict validation)
│ ├── process_animation()
│ │ ├── validate_user_name()
│ │ ├── _validate_animation_factory_creates_animation()
│ │ └── _process_named_arguments_for_animation()
│ ├── process_set()
│ │ ├── validate_user_name()
│ │ └── process_value()
│ ├── process_template()
│ │ ├── validate_user_name()
│ │ ├── Collect parameters with type annotations
│ │ ├── Collect body tokens
│ │ └── generate_template_function()
│ ├── process_sequence()
│ │ ├── validate_user_name()
│ │ ├── Parse repeat syntax (multiple variants)
│ │ └── process_sequence_statement() (fluent interface)
│ │ ├── process_play_statement_fluent()
│ │ ├── process_wait_statement_fluent()
│ │ ├── process_log_statement_fluent()
│ │ ├── process_restart_statement_fluent()
│ │ └── process_sequence_assignment_fluent()
│ ├── process_import() (direct Berry import generation)
│ ├── process_event_handler() (basic event system support)
│ ├── process_berry_code_block() (embed arbitrary Berry code)
│ ├── process_run() (collect for single engine.run())
│ └── process_property_assignment()
└── generate_engine_start() (single call for all run statements)
```
### Statement Processing Details
#### Color Processing
```
process_color()
├── expect_identifier() → color name
├── validate_user_name() → check against reserved names
├── expect_assign() → '='
├── Check if function call (color provider)
│ ├── Check template_definitions first
│ ├── _validate_color_provider_factory_exists()
│ ├── add("var name_ = animation.func(engine)")
│ ├── Track in symbol_table for validation
│ └── _process_named_arguments_for_color_provider()
└── OR process_value() → static color value with symbol tracking
```
#### Animation Processing
```
process_animation()
├── expect_identifier() → animation name
├── validate_user_name() → check against reserved names
├── expect_assign() → '='
├── Check if function call (animation factory)
│ ├── Check template_definitions first
│ ├── _validate_animation_factory_creates_animation()
│ ├── add("var name_ = animation.func(engine)")
│ ├── Track in symbol_table for validation
│ └── _process_named_arguments_for_animation()
└── OR process_value() → reference or literal with symbol tracking
```
#### Sequence Processing (Enhanced)
```
process_sequence()
├── expect_identifier() → sequence name
├── validate_user_name() → check against reserved names
├── Track in sequence_names and symbol_table
├── Parse multiple repeat syntaxes:
│ ├── "sequence name repeat N times { ... }"
│ ├── "sequence name forever { ... }"
│ ├── "sequence name N times { ... }"
│ └── "sequence name { repeat ... }"
├── expect_left_brace() → '{'
├── add("var name_ = animation.sequence_manager(engine, repeat_count)")
├── while !check_right_brace()
│ └── process_sequence_statement() (fluent interface)
└── expect_right_brace() → '}'
```
#### Template Processing
```
process_template()
├── expect_identifier() → template name
├── validate_user_name() → check against reserved names
├── expect_left_brace() → '{'
├── Sequential step 1: collect parameters with type annotations
├── Sequential step 2: collect body tokens
├── expect_right_brace() → '}'
├── Store in template_definitions
├── generate_template_function()
│ ├── Create new transpiler instance for body
│ ├── Transpile body with fresh symbol table
│ ├── Generate Berry function with engine parameter
│ └── Register as user function
└── Track in symbol_table as "template"
process_template_animation()
├── expect_identifier() → template animation name
├── validate_user_name() → check against reserved names
├── expect_left_brace() → '{'
├── Sequential step 1: collect parameters with constraints (type, min, max, default)
├── Sequential step 2: collect body tokens
├── expect_right_brace() → '}'
├── generate_template_animation_class()
│ ├── Generate class extending engine_proxy
│ ├── Generate PARAMS with encode_constraints
│ ├── Create new transpiler instance for body
│ ├── Set template_animation_params for special handling
│ │ ├── Add user-defined parameters
│ │ └── Add inherited parameters from engine_proxy hierarchy (dynamic discovery)
│ ├── Transpile body with self.param references
│ └── Use self.add() instead of engine.add()
└── Track in symbol_table as "template"
### Implicit Parameters in Template Animations
Template animations automatically inherit parameters from the `engine_proxy` class hierarchy. The transpiler dynamically discovers these parameters at compile time:
**Dynamic Parameter Discovery:**
```
_add_inherited_params_to_template(template_params_map)
├── Create temporary engine_proxy instance
├── Walk up class hierarchy using introspection
├── For each class with PARAMS:
│ └── Add all parameter names to template_params_map
└── Fallback to static list if instance creation fails
```
**Inherited Parameters (from Animation and ParameterizedObject):**
- `id` (string, default: "animation")
- `priority` (int, default: 10)
- `duration` (int, default: 0)
- `loop` (bool, default: false)
- `opacity` (int, default: 255)
- `color` (int, default: 0)
- `is_running` (bool, default: false)
**Parameter Resolution Order:**
1. Check if identifier is in `template_animation_params` (includes both user-defined and inherited)
2. If found, resolve as `self.<param>` (template animation parameter)
3. Otherwise, check symbol table for user-defined variables
4. If not found, raise "Unknown identifier" error
This allows template animations to use inherited parameters like `duration` and `opacity` without explicit declaration, while still maintaining type safety and validation.
```
## Expression Processing Chain
The transpiler uses a **unified recursive descent parser** for expressions with **raw mode support** for closure contexts:
```
process_value(context)
└── process_additive_expression(context, is_top_level=true, raw_mode=false)
├── process_multiplicative_expression(context, is_top_level, raw_mode)
│ ├── process_unary_expression(context, is_top_level, raw_mode)
│ │ └── process_primary_expression(context, is_top_level, raw_mode)
│ │ ├── Parenthesized expression → recursive call
│ │ ├── Function call handling:
│ │ │ ├── Raw mode: mathematical functions → animation._math.method()
│ │ │ ├── Raw mode: template calls → template_func(self.engine, ...)
│ │ │ ├── Regular mode: process_function_call() or process_nested_function_call()
│ │ │ └── Simple function detection → _is_simple_function_call()
│ │ ├── Color literal → convert_color() (enhanced ARGB support)
│ │ ├── Time literal → process_time_value() (with variable support)
│ │ ├── Percentage → process_percentage_value()
│ │ ├── Number literal → return as-is
│ │ ├── String literal → quote and return
│ │ ├── Array literal → process_array_literal() (not in raw mode)
│ │ ├── Identifier → enhanced symbol resolution
│ │ │ ├── Object property → "obj.prop" with validation
│ │ │ ├── User function → _process_user_function_call()
│ │ │ ├── Palette constant → "animation.PALETTE_RAINBOW" etc.
│ │ │ ├── Named color → get_named_color_value()
│ │ │ └── Consolidated symbol resolution → resolve_symbol_reference()
│ │ └── Boolean keywords → true/false
│ └── Handle unary operators (-, +)
└── Handle multiplicative operators (*, /)
└── Handle additive operators (+, -)
└── Closure wrapping logic:
├── Skip in raw_mode
├── Special handling for repeat_count context
├── is_computed_expression_string() detection
└── create_computation_closure_from_string()
```
### Expression Context Handling
The expression processor handles different contexts with **enhanced validation and processing**:
- **`"color"`** - Color definitions and assignments
- **`"animation"`** - Animation definitions and assignments
- **`"argument"`** - Function call arguments
- **`"property"`** - Property assignments with validation
- **`"variable"`** - Variable assignments with type tracking
- **`"repeat_count"`** - Sequence repeat counts (special closure handling)
- **`"time"`** - Time value processing with variable support
- **`"array_element"`** - Array literal elements
- **`"event_param"`** - Event handler parameters
- **`"expression"`** - Raw expression context (for closures)
### Computed Expression Detection (Enhanced)
The transpiler automatically detects computed expressions that need closures with **improved accuracy**:
```
is_computed_expression_string(expr_str)
├── Check for arithmetic operators (+, -, *, /) with spaces
├── Check for function calls (excluding simple functions)
│ ├── Extract function name before parenthesis
│ ├── Use _is_simple_function_call() to filter
│ └── Only mark complex functions as needing closures
├── Exclude simple parenthesized literals like (-1)
└── Return true only for actual computations
create_computation_closure_from_string(expr_str)
├── transform_expression_for_closure()
│ ├── Sequential step 1: Transform mathematical functions → animation._math.method()
│ │ ├── Use dynamic introspection with is_math_method()
│ │ ├── Check for existing "self." prefix /// TODO NOT SURE IT STILL EXISTS
│ │ └── Only transform if not already prefixed
│ ├── Sequential step 2: Transform user variables → animation.resolve(var_)
│ │ ├── Find variables ending with _
│ │ ├── Check for existing resolve() calls
│ │ ├── Avoid double-wrapping
│ │ └── Handle identifier character boundaries
│ └── Clean up extra spaces
└── Return "animation.create_closure_value(engine, closure)"
is_anonymous_function(expr_str)
├── Check if expression starts with "(def "
├── Check if expression ends with ")(engine)"
└── Skip closure wrapping for already-wrapped functions
```
## Enhanced Symbol Table System
The transpiler uses a sophisticated **SymbolTable** system for holistic symbol management and caching. This system provides dynamic symbol detection, type validation, and conflict prevention.
### SymbolTable Architecture
The symbol table consists of two main classes in `symbol_table.be`:
#### SymbolEntry Class
```
SymbolEntry
├── name: string # Symbol name
├── type: string # Symbol type classification
├── instance: object # Actual instance for validation
├── takes_args: boolean # Whether symbol accepts arguments
├── arg_type: string # "positional", "named", or "none"
└── is_builtin: boolean # Whether this is a built-in symbol from animation module
```
**Symbol Types Supported:**
- `"palette"` - Palette objects like `PALETTE_RAINBOW` (bytes instances)
- `"constant"` - Integer constants like `LINEAR`, `SINE`, `COSINE`
- `"math_function"` - Mathematical functions like `max`, `min`
- `"user_function"` - User-defined functions registered at runtime
- `"value_provider"` - Value provider constructors
- `"animation"` - Animation constructors
- `"color"` - Color definitions and providers
- `"variable"` - User-defined variables
- `"sequence"` - Sequence definitions
- `"template"` - Template definitions
#### SymbolTable Class
```
SymbolTable
├── entries: map # Map of name -> SymbolEntry
├── mock_engine: MockEngine # For validation testing
├── Dynamic Detection Methods:
│ ├── _detect_and_cache_symbol() # On-demand symbol detection
│ ├── contains() # Existence check with auto-detection
│ └── get() # Retrieval with auto-detection
├── Creation Methods:
│ ├── create_palette()
│ ├── create_color()
│ ├── create_animation()
│ ├── create_value_provider()
│ ├── create_variable()
│ ├── create_sequence()
│ └── create_template()
└── Validation Methods:
├── symbol_exists()
├── get_reference()
└── takes_args() / takes_positional_args() / takes_named_args()
```
### Dynamic Symbol Detection
The SymbolTable uses **lazy detection** to identify and cache symbols as they are encountered:
```
_detect_and_cache_symbol(name)
├── Check if already cached → return cached entry
├── Check animation module using introspection:
│ ├── Detect bytes() instances → create_palette()
│ ├── Detect integer constants (type == "int") → create_constant()
│ ├── Detect math functions in animation._math → create_math_function()
│ ├── Detect user functions via animation.is_user_function() → create_user_function()
│ ├── Test constructors with MockEngine:
│ │ ├── Create instance with mock_engine
│ │ ├── Check isinstance(instance, animation.value_provider) → create_value_provider()
│ │ └── Check isinstance(instance, animation.animation) → create_animation()
│ └── Cache result for future lookups
└── Return nil if not found (handled as user-defined)
```
### Symbol Type Detection Examples
**Palette Detection:**
```berry
# DSL: animation rainbow = rich_palette_animation(colors=PALETTE_RAINBOW)
# Detection: PALETTE_RAINBOW exists in animation module, isinstance(obj, bytes)
# Result: SymbolEntry("PALETTE_RAINBOW", "palette", bytes_instance, true)
# Reference: "animation.PALETTE_RAINBOW"
```
**Constant Detection:**
```berry
# DSL: animation wave = wave_animation(waveform=LINEAR)
# Detection: LINEAR exists in animation module, type(LINEAR) == "int"
# Result: SymbolEntry("LINEAR", "constant", 1, true)
# Reference: "animation.LINEAR"
```
**Math Function Detection:**
```berry
# DSL: animation.opacity = max(100, min(255, brightness))
# Detection: max exists in animation._math, is callable
# Result: SymbolEntry("max", "math_function", nil, true)
# Reference: "animation.max" (transformed to "animation._math.max" in closures)
```
**Value Provider Detection:**
```berry
# DSL: set oscillator = triangle(min_value=0, max_value=100, period=2s)
# Detection: triangle(mock_engine) creates instance, isinstance(instance, animation.value_provider)
# Result: SymbolEntry("triangle", "value_provider", instance, true)
# Reference: "animation.triangle"
```
**User Function Detection:**
```berry
# DSL: animation demo = rand_demo(color=red)
# Detection: animation.is_user_function("rand_demo") returns true
# Result: SymbolEntry("rand_demo", "user_function", nil, true)
# Reference: "rand_demo_" (handled specially in function calls)
```
### Symbol Conflict Prevention
The SymbolTable prevents symbol redefinition conflicts:
```
add(name, entry)
├── Check for built-in symbol conflicts:
│ ├── _detect_and_cache_symbol(name)
│ └── Raise "symbol_redefinition_error" if types differ
├── Check existing user-defined symbols:
│ ├── Compare entry.type with existing.type
│ └── Raise "symbol_redefinition_error" if types differ
├── Allow same-type updates (reassignment)
└── Return entry for method chaining
```
**Example Conflict Detection:**
```berry
# This would raise an error:
color max = 0xFF0000 # Conflicts with built-in math function "max"
# This would also raise an error:
color red = 0xFF0000
animation red = solid(color=blue) # Redefining "red" as different type
```
### Integration with Transpiler
The SymbolTable integrates seamlessly with the transpiler's processing flow:
### Performance Optimizations
**Caching Strategy:**
- **Lazy Detection**: Symbols detected only when first encountered
- **Instance Reuse**: MockEngine instances reused for validation
- **Introspection Caching**: Built-in symbol detection cached permanently
**Memory Efficiency:**
- **Minimal Storage**: Only essential information stored per symbol
- **Shared MockEngine**: Single MockEngine instance for all validation
- **Reference Counting**: Automatic cleanup of unused entries
### MockEngine Integration
The SymbolTable uses a lightweight MockEngine for constructor validation:
```
MockEngine
├── time_ms: 0 # Mock time for validation
├── get_strip_length(): 30 # Default strip length
└── Minimal interface for instance creation testing
```
**Usage in Detection:**
```berry
# Test if function creates value provider
try
var instance = factory_func(self.mock_engine)
if isinstance(instance, animation.value_provider)
return SymbolEntry.create_value_provider(name, instance, animation.value_provider)
end
except .. as e, msg
# Constructor failed - not a valid provider
end
```
## Validation System (Comprehensive)
The transpiler includes **extensive compile-time validation** with robust error handling:
### Factory Function Validation (Simplified using SymbolTable)
```
_validate_animation_factory_exists(func_name)
├── Skip validation for mathematical functions
├── Use symbol_table.get(func_name) for dynamic detection
└── Return true if entry exists (any callable function is valid)
_validate_animation_factory_creates_animation(func_name)
├── Use symbol_table.get(func_name) for dynamic detection
└── Return true if entry.type == "animation"
_validate_color_provider_factory_onsts(func_name)
├── Use symbol_table.get(func_name) for dynamic detection
└── Return true if entry exists (any callable function is valid)
_validate_value_provider_factory_exists(func_name)
├── Use symbol_table.get(func_name) for dynamic detection
└── Return true if entry.type == "value_provider"
```
### Parameter Validation (Real-time)
```
_validate_single_parameter(func_name, param_name, animation_instance)
├── Use introspection to check if parameter exists
├── Call instance.has_param(param_name) for validation
├── Report detailed error messages with line numbers
├── Validate immediately as parameters are parsed
└── Graceful error handling to ensure transpiler robustness
_create_instance_for_validation(func_name) - Simplified using SymbolTable
├── Use symbol_table.get(func_name) for dynamic detection
└── Return entry.instance if available, nil otherwise
```
### Reference Validation (Simplified using SymbolTable)
```
resolve_symbol_reference(name) - Simplified using SymbolTable
└── Use symbol_table.get_reference(name) for all symbol resolution
validate_symbol_reference(name, context) - With error reporting
├── Use symbol_exists() to check symbol_table
├── Report detailed error with context information
└── Return validation status
symbol_exists(name) - Simplified existence check
└── Use symbol_table.symbol_exists(name) for unified checking
_validate_value_provider_reference(object_name, context) - Simplified
├── Check symbol_exists() using symbol_table
├── Use symbol_table.get(name) for type information
├── Check entry.type == "value_provider" || entry.type == "animation"
└── Report detailed error messages for invalid types
```
### User Name Validation (Reserved Names)
```
validate_user_name(name, definition_type)
├── Check against predefined color names
├── Check against DSL statement keywords
├── Report conflicts with suggestions for alternatives
└── Prevent redefinition of reserved identifiers
```
### Value Provider Validation (New)
```
_validate_value_provider_reference(object_name, context)
├── Check if symbol exists using validate_symbol_reference()
├── Check symbol_table markers for type information
├── Validate instance types using isinstance()
├── Ensure only value providers/animations can be restarted
└── Provide detailed error messages for invalid types
```
## Code Generation Patterns
### Engine-First Pattern (Consistent)
All factory functions use the engine-first pattern with **automatic strip initialization**:
```berry
# DSL: animation pulse = pulsating_animation(color=red, period=2s)
# Generated:
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var pulse_ = animation.pulsating_animation(engine)
pulse_.color = animation.red
pulse_.period = 2000
```
**Template-Only Exception**: Files containing only template definitions skip engine initialization and `engine.run()` generation, producing pure function libraries.
### Symbol Resolution (Consolidated)
The transpiler resolves symbols at compile time using **unified resolution logic** based on the `is_builtin` flag:
```berry
# Built-in symbols (is_builtin=true) from animation module → animation.symbol
animation.linear, animation.PALETTE_RAINBOW, animation.SINE, animation.solid
# User-defined symbols (is_builtin=false) → symbol_
my_color_, my_animation_, my_sequence_
# Named colors → direct ARGB values (resolved at compile time)
red → 0xFFFF0000, blue → 0xFF0000FF
# Template calls → template_function(engine, args)
my_template(red, 2s) → my_template_template(engine, 0xFFFF0000, 2000)
### Closure Generation (Enhanced)
Dynamic expressions are wrapped in closures with **mathematical function support**:
```berry
# DSL: animation.opacity = strip_length() / 2 + 50
# Generated:
animation.opacity = animation.create_closure_value(engine,
def (self) return animation.resolve(strip_length_(engine)) / 2 + 50 end)
# DSL: animation.opacity = max(100, min(255, rand_demo() + 50))
# Generated:
animation.opacity = animation.create_closure_value(engine,
def (self) return animation._math.max(100, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 50)) end)
# Mathematical functions are automatically detected and prefixed with animation._math.
# User functions are wrapped with animation.get_user_function() calls
```
### Template Generation (New)
Templates are transpiled into Berry functions and registered as user functions:
```berry
# DSL Template:
template pulse_effect {
param color type color
param speed
animation pulse = pulsating_animation(color=color, period=speed)
run pulse
}
# Generated:
def pulse_effect_template(engine, color_, speed_)
var pulse_ = animation.pulsating_animation(engine)
pulse_.color = color_
pulse_.period = speed_
engine.add(pulse_)
end
animation.register_user_function('pulse_effect', pulse_effect_template)
```
### Sequence Generation (Fluent Interface)
Sequences use fluent interface pattern for better readability:
```berry
# DSL: sequence demo { play anim for 2s; wait 1s }
# Generated:
var demo_ = animation.sequence_manager(engine)
.push_play_step(anim_, 2000)
.push_wait_step(1000)
# Nested repeats use sub-sequences:
var demo_ = animation.sequence_manager(engine)
.push_repeat_subsequence(animation.sequence_manager(engine, 3)
.push_play_step(anim_, 1000)
)
```
## Template System (Enhanced)
Templates are transpiled into Berry functions with **comprehensive parameter handling**:
**Template-Only Optimization**: Files containing only template definitions skip engine initialization and execution code generation, producing pure Berry function libraries.
```
process_template()
├── expect_identifier() → template name
├── validate_user_name() → check against reserved names
├── expect_left_brace() → '{'
├── Sequential step 1: collect parameters with type annotations
│ ├── Parse "param name type annotation" syntax
│ ├── Store parameter names and optional types
│ └── Support both typed and untyped parameters
├── Sequential step 2: collect body tokens until closing brace
│ ├── Handle nested braces correctly
│ ├── Preserve all tokens for later transpilation
│ └── Track brace depth for proper parsing
├── expect_right_brace() → '}'
├── Store in template_definitions for call resolution
├── generate_template_function()
│ ├── Create new SimpleDSLTranspiler instance for body
│ ├── Set up fresh symbol table with parameters
│ ├── Mark strip as initialized (templates assume engine exists)
│ ├── Transpile body using transpile_template_body()
│ ├── Generate Berry function with engine + parameters
│ ├── Handle transpilation errors gracefully
│ └── Register as user function automatically
└── Track in symbol_table as "template"
```
### Template Call Resolution (Multiple Contexts)
```berry
# DSL template call in animation context:
animation my_anim = my_template(red, 2s)
# Generated: var my_anim_ = my_template_template(engine, 0xFFFF0000, 2000)
# DSL template call in property context:
animation.opacity = my_template(blue, 1s)
# Generated: animation.opacity = my_template_template(self.engine, 0xFF0000FF, 1000)
# DSL standalone template call:
my_template(green, 3s)
# Generated: my_template_template(engine, 0xFF008000, 3000)
```
### Template Body Transpilation
Templates use a **separate transpiler instance** with isolated symbol table:
- Fresh symbol table prevents name conflicts
- Parameters are added as "parameter" markers
- Run statements are processed immediately (not collected)
- Template calls can be nested (templates calling other templates)
- Error handling preserves context information
## Error Handling (Robust)
The transpiler provides **comprehensive error reporting** with graceful degradation:
### Error Categories
- **Syntax errors** - Invalid DSL syntax with line numbers
- **Factory validation** - Non-existent animation/color factories with suggestions
- **Parameter validation** - Invalid parameter names with class context
- **Reference validation** - Undefined object references with context information
- **Constraint validation** - Parameter values outside valid ranges
- **Type validation** - Incorrect parameter types with expected types
- **Safety validation** - Dangerous patterns that could cause memory leaks or performance issues
- **Template errors** - Template definition and call validation
- **Reserved name conflicts** - User names conflicting with built-ins
### Error Reporting Features
```berry
error(msg)
├── Capture current line number from token
├── Format error with context: "Line X: message"
├── Store in errors array for batch reporting
└── Continue transpilation for additional error discovery
get_error_report()
├── Check if errors exist
├── Format comprehensive error report
├── Include all errors with line numbers
└── Provide user-friendly error messages
```
### Graceful Error Handling
- **Try-catch blocks** around validation to prevent crashes
- **Robust validation** that continues on individual failures
- **Skip statement** functionality to recover from parse errors
- **Default values** when validation fails to maintain transpilation flow
- **Context preservation** in error messages for better debugging
## Performance Considerations
### Ultra-Simplified Architecture
- **Single-pass processing** - tokens processed once from start to finish
- **Incremental symbol table** - builds validation context as it parses
- **Immediate validation** - catches errors as soon as they're encountered
- **Minimal state tracking** - only essential information is maintained
### Compile-Time Optimization
- **Symbol resolution at transpile time** - eliminates runtime lookups
- **Parameter validation during parsing** - catches errors early
- **Template pre-compilation** - templates become efficient Berry functions
- **Closure detection** - only wraps expressions that actually need it
- **Mathematical function detection** - uses dynamic introspection for accuracy
### Memory Efficiency
- **Streaming token processing** - no large intermediate AST structures
- **Direct code generation** - output generated as parsing proceeds
- **Minimal intermediate representations** - tokens and symbol table only
- **Template isolation** - separate transpiler instances prevent memory leaks
- **Graceful error handling** - prevents memory issues from validation failures
### Validation Efficiency
- **MockEngine pattern** - lightweight validation without full engine
- **Introspection caching** - validation results can be cached
- **Early termination** - stops processing invalid constructs quickly
- **Batch error reporting** - collects multiple errors in single pass
## Integration Points
### Animation Module Integration
- **Factory function discovery** via introspection with existence checking
- **Parameter validation** using instance methods and has_param()
- **Symbol resolution** using module contents with fallback handling
- **Mathematical function detection** using dynamic introspection of ClosureValueProvider
- **Automatic strip initialization** when no explicit strip configuration
### User Function Integration
- **Template registration** as user functions with automatic naming
- **User function call detection** usable as normal functions with positional arguments
- **Closure generation** for computed parameters with mathematical functions
- **Template call resolution** in multiple contexts (animation, property, standalone)
- **Import statement processing** for user function modules
### DSL Language Integration
- **Comment preservation** in generated Berry code
- **Inline comment handling** with proper spacing
- **Multiple syntax support** for sequences (repeat variants)
- **Palette syntax flexibility** (tuple vs alternative syntax)
- **Time unit conversion** with variable support
- **Percentage conversion** to 0-255 range
### Robustness Features
- **Graceful error recovery** - continues parsing after errors
- **Validation isolation** - validation failures don't crash transpiler
- **Symbol table tracking** - maintains context for validation
- **Template isolation** - separate transpiler instances prevent conflicts
- **Reserved name protection** - prevents conflicts with built-in identifiers
## Key Architectural Changes
The refactored transpiler emphasizes:
1. **Simplicity** - Ultra-simplified single-pass architecture
2. **Robustness** - Comprehensive error handling and graceful degradation
3. **Enhanced Symbol Management** - Dynamic SymbolTable system with intelligent caching and conflict detection
4. **Validation** - Extensive compile-time validation with detailed error messages
5. **Flexibility** - Support for templates, multiple syntax variants, and user functions
6. **Performance** - Efficient processing with minimal memory overhead and lazy symbol detection
7. **Maintainability** - Clear separation of concerns and unified processing methods
## Recent Refactoring Improvements
### Code Simplification Using SymbolTable
The transpiler has been significantly refactored to leverage the `symbol_table.be` system more extensively:
#### **Factory Validation Simplification**
- **Before**: Complex validation with introspection and manual instance creation (~50 lines)
- **After**: Simple validation using symbol_table's dynamic detection (~25 lines)
- **Improvement**: 50% code reduction with better maintainability
#### **Symbol Resolution Consolidation**
- **Before**: Multiple separate checks for sequences, introspection, etc.
- **After**: Unified resolution through `symbol_table.get_reference()`
- **Improvement**: Single source of truth for all symbol resolution
#### **Duplicate Code Elimination**
- **Before**: Duplicate code patterns in `process_color()` and `process_animation()` methods
- **After**: Consolidated into reusable `_process_simple_value_assignment()` helper
- **Improvement**: 70% reduction in duplicate code blocks
#### **Legacy Variable Removal**
- **Before**: Separate tracking of sequences in `sequence_names` variable
- **After**: All symbols tracked uniformly in `symbol_table`
- **Improvement**: Eliminated redundancy and simplified state management
### Major Enhancements
**SymbolTable System:**
- **Dynamic Detection**: Automatically detects and caches symbol types as encountered
- **Conflict Prevention**: Prevents redefinition of symbols with different types
- **Performance Optimization**: Lazy loading and efficient symbol resolution for optimal performance
- **Type Safety**: Comprehensive type checking with MockEngine validation
- **Modular Design**: Separated into `symbol_table.be` for reusability
- **Constant Detection**: Added support for integer constants like `LINEAR`, `SINE`, `COSINE`
**Enhanced Symbol Detection:**
- **Palette Objects**: `PALETTE_RAINBOW``animation.PALETTE_RAINBOW`
- **Integer Constants**: `LINEAR`, `SINE`, `COSINE``animation.LINEAR`, `animation.SINE`, `animation.COSINE`
- **Math Functions**: `max`, `min``animation.max`, `animation.min` (transformed to `animation._math.*` in closures)
- **Value Providers**: `triangle`, `smooth``animation.triangle`, `animation.smooth`
- **Animation Constructors**: `solid`, `pulsating_animation``animation.solid`, `animation.pulsating_animation`
- **User-defined Symbols**: `my_color`, `my_animation``my_color_`, `my_animation_`
**Validation Improvements:**
- **Real-time Validation**: Parameter validation as symbols are parsed
- **Instance-based Checking**: Uses actual instances for accurate validation
- **Graceful Error Handling**: Robust error recovery with detailed error messages
- **Simplified Validation Methods**: Factory validation reduced from ~50 to ~25 lines using symbol_table
- **Unified Symbol Checking**: All symbol existence checks go through symbol_table system
- **Enhanced Type Detection**: Automatic detection of constants, palettes, functions, and constructors
This architecture ensures robust, efficient transpilation from DSL to executable Berry code while providing comprehensive validation, detailed error reporting, intelligent symbol management, and extensive language features.
### Symbol Reference Generation
The enhanced SymbolEntry system uses the `is_builtin` flag to determine correct reference generation:
```berry
# SymbolEntry.get_reference() method
def get_reference()
if self.is_builtin
return f"animation.{self.name}" # Built-in symbols: animation.LINEAR
else
return f"{self.name}_" # User-defined symbols: my_color_
end
end
```
**Examples:**
- **Built-in Constants**: `LINEAR``animation.LINEAR`
- **Built-in Functions**: `triangle``animation.triangle`
- **Built-in Palettes**: `PALETTE_RAINBOW``animation.PALETTE_RAINBOW`
- **User-defined Colors**: `my_red``my_red_`
- **User-defined Animations**: `pulse_anim``pulse_anim_`
This ensures consistent and correct symbol resolution throughout the transpilation process.

View File

@ -1,677 +0,0 @@
# User-Defined Functions
Create custom animation functions in Berry and use them seamlessly in the Animation DSL.
## Quick Start
### 1. Create Your Function
Write a Berry function that creates and returns an animation:
```berry
# Define a custom breathing effect
def my_breathing(engine, color, speed)
var anim = animation.pulsating_animation(engine)
anim.color = color
anim.min_brightness = 50
anim.max_brightness = 255
anim.period = speed
return anim
end
```
### 2. Register It
Make your function available in DSL:
```berry
animation.register_user_function("breathing", my_breathing)
```
### 3. Use It in DSL
First, import your user functions module, then call your function directly in computed parameters:
```berry
# Import your user functions module
import user_functions
# Use your custom function in computed parameters
animation calm = solid(color=blue)
calm.opacity = breathing_effect()
animation energetic = solid(color=red)
energetic.opacity = breathing_effect()
sequence demo {
play calm for 10s
play energetic for 5s
}
run demo
```
## Importing User Functions
### DSL Import Statement
The DSL supports importing Berry modules using the `import` keyword. This is the recommended way to make user functions available in your animations:
```berry
# Import user functions at the beginning of your DSL file
import user_functions
# Now user functions are available directly
animation test = solid(color=blue)
test.opacity = my_function()
```
### Import Behavior
- **Module Loading**: `import user_functions` transpiles to Berry `import "user_functions"`
- **Function Registration**: The imported module should register functions using `animation.register_user_function()`
- **Availability**: Once imported, functions are available throughout the DSL file
- **No Compile-Time Checking**: The DSL doesn't validate user function existence at compile time
### Example User Functions Module
Create a file called `user_functions.be`:
```berry
import animation
# Define your custom functions
def rand_demo(engine)
import math
return math.rand() % 256 # Random value 0-255
end
def breathing_effect(engine, base_value, amplitude)
import math
var time_factor = (engine.time_ms / 1000) % 4 # 4-second cycle
var breath = math.sin(time_factor * math.pi / 2)
return int(base_value + breath * amplitude)
end
# Register functions for DSL use
animation.register_user_function("rand_demo", rand_demo)
animation.register_user_function("breathing", breathing_effect)
print("User functions loaded!")
```
### Using Imported Functions in DSL
```berry
import user_functions
# Simple user function call
animation random_test = solid(color=red)
random_test.opacity = rand_demo()
# User function with parameters
animation breathing_blue = solid(color=blue)
breathing_blue.opacity = breathing(128, 64)
# User functions in mathematical expressions
animation complex = solid(color=green)
complex.opacity = max(50, min(255, rand_demo() + 100))
run random_test
```
### Multiple Module Imports
You can import multiple modules in the same DSL file:
```berry
import user_functions # Basic user functions
import fire_effects # Fire animation functions
import color_utilities # Color manipulation functions
animation base = solid(color=random_color())
base.opacity = breathing(200, 50)
animation flames = solid(color=red)
flames.opacity = fire_intensity(180)
```
## Common Patterns
### Simple Color Effects
```berry
def solid_bright(engine, color, brightness_percent)
var anim = animation.solid_animation(engine)
anim.color = color
anim.brightness = int(brightness_percent * 255 / 100)
return anim
end
animation.register_user_function("bright", solid_bright)
```
```berry
animation bright_red = solid(color=red)
bright_red.opacity = bright(80)
animation dim_blue = solid(color=blue)
dim_blue.opacity = bright(30)
```
### Fire Effects
```berry
def custom_fire(engine, intensity, speed)
var color_provider = animation.rich_palette(engine)
color_provider.colors = animation.PALETTE_FIRE
color_provider.period = speed
var fire_anim = animation.filled(engine)
fire_anim.color_provider = color_provider
fire_anim.brightness = intensity
return fire_anim
end
animation.register_user_function("fire", custom_fire)
```
```berry
animation campfire = solid(color=red)
campfire.opacity = fire(200, 2000)
animation torch = solid(color=orange)
torch.opacity = fire(255, 500)
```
### Twinkling Effects
```berry
def twinkles(engine, color, count, period)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.count = count
anim.period = period
return anim
end
animation.register_user_function("twinkles", twinkles)
```
```berry
animation stars = solid(color=white)
stars.opacity = twinkles(12, 800ms)
animation fairy_dust = solid(color=0xFFD700)
fairy_dust.opacity = twinkles(8, 600ms)
```
### Position-Based Effects
```berry
def pulse_at(engine, color, position, width, speed)
var anim = animation.beacon_animation(engine)
anim.color = color
anim.position = position
anim.width = width
anim.period = speed
return anim
end
animation.register_user_function("pulse_at", pulse_at)
```
```berry
animation left_pulse = solid(color=green)
left_pulse.position = pulse_at(5, 3, 2000)
animation right_pulse = solid(color=blue)
right_pulse.position = pulse_at(25, 3, 2000)
```
## Advanced Examples
### Multi-Layer Effects
```berry
def rainbow_twinkle(engine, base_speed, twinkle_density)
# Create base rainbow animation
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.colors = animation.PALETTE_RAINBOW
rainbow_provider.period = base_speed
var base_anim = animation.filled(engine)
base_anim.color_provider = rainbow_provider
base_anim.priority = 1
# Note: This is a simplified example
# Real multi-layer effects would require engine support
return base_anim
end
animation.register_user_function("rainbow_sparkle", rainbow_sparkle)
```
### Dynamic Palettes
Since DSL palettes only accept hex colors and predefined color names (not custom colors), use user functions for dynamic palettes with custom colors:
```berry
def create_custom_palette(engine, base_color, variation_count, intensity)
# Create a palette with variations of the base color
var palette_bytes = bytes()
# Extract RGB components from base color
var r = (base_color >> 16) & 0xFF
var g = (base_color >> 8) & 0xFF
var b = base_color & 0xFF
# Create palette entries with color variations
for i : 0..(variation_count-1)
var position = int(i * 255 / (variation_count - 1))
var factor = intensity * i / (variation_count - 1) / 255
var new_r = int(r * factor)
var new_g = int(g * factor)
var new_b = int(b * factor)
# Add VRGB entry (Value, Red, Green, Blue)
palette_bytes.add(position, 1) # Position
palette_bytes.add(new_r, 1) # Red
palette_bytes.add(new_g, 1) # Green
palette_bytes.add(new_b, 1) # Blue
end
return palette_bytes
end
animation.register_user_function("custom_palette", create_custom_palette)
```
```berry
# Use dynamic colors in DSL
animation gradient_effect = rich_palette(
colors=custom_palette(0xFF6B35, 5, 255)
period=4s
)
run gradient_effect
```
### Preset Configurations
```berry
def police_lights(engine, flash_speed)
var anim = animation.pulsating_animation(engine)
anim.color = 0xFFFF0000 # Red
anim.min_brightness = 0
anim.max_brightness = 255
anim.period = flash_speed
return anim
end
def warning_strobe(engine)
return police_lights(engine, 200) # Fast strobe
end
def gentle_alert(engine)
return police_lights(engine, 1000) # Slow pulse
end
animation.register_user_function("police", police_lights)
animation.register_user_function("strobe", warning_strobe)
animation.register_user_function("alert", gentle_alert)
```
```berry
animation emergency = solid(color=red)
emergency.opacity = strobe()
animation notification = solid(color=yellow)
notification.opacity = alert()
animation custom_police = solid(color=blue)
custom_police.opacity = police(500)
```
## Function Organization
### Single File Approach
```berry
# user_animations.be
import animation
def breathing(engine, color, period)
# ... implementation
end
def fire_effect(engine, intensity, speed)
# ... implementation
end
def twinkle_effect(engine, color, count, period)
# ... implementation
end
# Register all functions
animation.register_user_function("breathing", breathing)
animation.register_user_function("fire", fire_effect)
animation.register_user_function("twinkle", twinkle_effect)
print("Custom animations loaded!")
```
### Modular Approach
```berry
# animations/fire.be
def fire_effect(engine, intensity, speed)
# ... implementation
end
def torch_effect(engine)
return fire_effect(engine, 255, 500)
end
return {
'fire': fire_effect,
'torch': torch_effect
}
```
```berry
# main.be
import animation
# Register functions
animation.register_user_function("fire", fire_effects['fire'])
animation.register_user_function("torch", fire_effects['torch'])
```
## Best Practices
### Function Design
1. **Use descriptive names**: `breathing_slow` not `bs`
2. **Logical parameter order**: color first, then timing, then modifiers
3. **Sensible defaults**: Make functions work with minimal parameters
4. **Return animations**: Always return a configured animation object
### Parameter Handling
```berry
def flexible_pulse(engine, color, period, min_brightness, max_brightness)
# Provide defaults for optional parameters
if min_brightness == nil min_brightness = 50 end
if max_brightness == nil max_brightness = 255 end
var anim = animation.pulsating_animation(engine)
anim.color = color
anim.period = period
anim.min_brightness = min_brightness
anim.max_brightness = max_brightness
return anim
end
```
### Error Handling
```berry
def safe_comet(engine, color, tail_length, speed)
# Validate parameters
if tail_length < 1 tail_length = 1 end
if tail_length > 20 tail_length = 20 end
if speed < 100 speed = 100 end
var anim = animation.comet_animation(engine)
anim.color = color
anim.tail_length = tail_length
anim.speed = speed
return anim
end
```
### Documentation
```berry
# Creates a pulsing animation with customizable brightness range
# Parameters:
# color: The color to pulse (hex or named color)
# period: How long one pulse cycle takes (in milliseconds)
# min_brightness: Minimum brightness (0-255, default: 50)
# max_brightness: Maximum brightness (0-255, default: 255)
# Returns: Configured pulse animation
def breathing_effect(engine, color, period, min_brightness, max_brightness)
# ... implementation
end
```
## User Functions in Computed Parameters
User functions can be used in computed parameter expressions alongside mathematical functions, creating powerful dynamic animations:
### Simple User Function in Computed Parameter
```berry
# Simple user function call in property assignment
animation base = solid(color=blue, priority=10)
base.opacity = rand_demo() # User function as computed parameter
```
### User Functions with Mathematical Operations
```berry
# Get strip length for calculations
set strip_len = strip_length()
# Mix user functions with mathematical functions
animation dynamic_solid = solid(
color=purple
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
priority=15
)
```
### User Functions in Complex Expressions
```berry
# Use user function in arithmetic expressions
animation random_effect = solid(
color=cyan
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
priority=12
)
```
### How It Works
When you use user functions in computed parameters:
1. **Automatic Detection**: The transpiler automatically detects user functions in expressions
2. **Single Closure**: The entire expression is wrapped in a single efficient closure
3. **Engine Access**: User functions receive `engine` in the closure context
4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic
**Generated Code Example:**
```berry
# DSL code
animation.opacity = max(100, breathing(red, 2000))
```
**Transpiles to:**
```berry
animation.opacity = animation.create_closure_value(engine,
def (engine, param_name, time_ms)
return (animation._math.max(100, animation.get_user_function('breathing')(engine, 0xFFFF0000, 2000)))
end)
```
### Available User Functions
The following user functions are available by default:
| Function | Parameters | Description |
|----------|------------|-------------|
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
### Best Practices for Computed Parameters
1. **Keep expressions readable**: Break complex expressions across multiple lines
2. **Use meaningful variable names**: `set strip_len = strip_length()` not `set s = strip_length()`
3. **Combine wisely**: Mix user functions with math functions for rich effects
4. **Test incrementally**: Start simple and build up complex expressions
## Loading and Using Functions
### In Tasmota autoexec.be
```berry
import animation
# Load your custom functions
load("user_animations.be")
# Now they're available in DSL with import
var dsl_code =
"import user_functions\n"
"\n"
"animation my_fire = solid(color=red)\n"
"my_fire.opacity = fire(200, 1500)\n"
"animation my_twinkles = solid(color=white)\n"
"my_twinkles.opacity = twinkle(8, 400ms)\n"
"\n"
"sequence show {\n"
" play my_fire for 10s\n"
" play my_twinkles for 5s\n"
"}\n"
"\n"
"run show"
animation_dsl.execute(dsl_code)
```
### From Files
```berry
# Save DSL with custom functions
var my_show =
"import user_functions\n"
"\n"
"animation campfire = solid(color=orange)\n"
"campfire.opacity = fire(180, 2000)\n"
"animation stars = solid(color=0xFFFFFF)\n"
"stars.opacity = twinkle(6, 600ms)\n"
"\n"
"sequence night_scene {\n"
" play campfire for 30s\n"
" play stars for 10s\n"
"}\n"
"\n"
"run night_scene"
# Save to file
var f = open("night_scene.anim", "w")
f.write(my_show)
f.close()
# Load and run
animation_dsl.load_file("night_scene.anim")
```
## Implementation Details
### Function Signature Requirements
User functions must follow this exact pattern:
```berry
def function_name(engine, param1, param2, ...)
# engine is ALWAYS the first parameter
# followed by user-provided parameters
return animation_object
end
```
### How the DSL Transpiler Works
When you write DSL like this:
```berry
animation my_anim = my_function(arg1, arg2)
```
The transpiler generates Berry code like this:
```berry
var my_anim_ = animation.get_user_function('my_function')(engine, arg1, arg2)
```
The `engine` parameter is automatically inserted as the first argument.
### Registration API
```berry
# Register a function
animation.register_user_function(name, function)
# Check if a function is registered
if animation.is_user_function("my_function")
print("Function is registered")
end
# Get a registered function
var func = animation.get_user_function("my_function")
# List all registered functions
var functions = animation.list_user_functions()
for name : functions
print("Registered:", name)
end
```
### Engine Parameter
The `engine` parameter provides:
- Access to the LED strip: `engine.get_strip_length()`
- Current time: `engine.time_ms`
- Animation management context
Always use the provided engine when creating animations - don't create your own engine instances.
### Return Value Requirements
User functions must return an animation object that:
- Extends `animation.animation` or `animation.pattern`
- Is properly configured with the engine
- Has all required parameters set
### Error Handling
The framework handles errors gracefully:
- Invalid function names are caught at DSL compile time
- Runtime errors in user functions are reported with context
- Failed function calls don't crash the animation system
## Troubleshooting
### Function Not Found
```
Error: Unknown function 'my_function'
```
- Ensure the function is registered with `animation.register_user_function()`
- Check that registration happens before DSL compilation
- Verify the function name matches exactly (case-sensitive)
### Wrong Number of Arguments
```
Error: Function call failed
```
- Check that your function signature matches the DSL call
- Remember that `engine` is automatically added as the first parameter
- Verify all required parameters are provided in the DSL
### Animation Not Working
- Ensure your function returns a valid animation object
- Check that the animation is properly configured
- Verify that the engine parameter is used correctly
User-defined functions provide a powerful way to extend the Animation DSL with custom effects while maintaining the clean, declarative syntax that makes the DSL easy to use.