12 KiB
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:
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)
DSL API Functions
Core Functions
animation_dsl.compile(source)
Compiles DSL source code to Berry code without executing it.
var dsl_source = "color red = #FF0000\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.
animation_dsl.execute("color blue = #0000FF\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.
# Create a DSL file
var f = open("my_animation.dsl", "w")
f.write("color green = #00FF00\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")
Runtime Management
animation_dsl.create_runtime()
Creates a DSL runtime instance for advanced control.
var runtime = animation_dsl.create_runtime()
runtime.load_dsl(dsl_source)
runtime.execute()
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
- Named parameters: All function calls use
name=valuesyntax - Time units:
2s,500ms,1m,1h - Hex colors:
#FF0000,#80FF0000(ARGB) - Named colors:
red,blue,white, etc. - Comments:
# This is a comment - Property assignment:
animation.property = value
Basic Structure
# Optional strip configuration
strip length 60
# Color definitions
color red = #FF0000
color blue = #0000FF
# 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
pulse_red.priority = 10
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.
Advanced DSL Features
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:
# Define custom function in Berry - engine must be first parameter
def custom_sparkle(engine, color, density, speed)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.density = density
anim.speed = speed
return anim
end
# Register the function for DSL use
animation.register_user_function("sparkle", custom_sparkle)
# Use in DSL - engine is automatically passed as first argument
animation gold_sparkle = sparkle(#FFD700, 8, 500ms)
animation blue_sparkle = sparkle(blue, 12, 300ms)
run gold_sparkle
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.
Event System
Define event handlers that respond to triggers:
# Define animations for different states
color normal = #000080
color alert = #FF0000
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:
# Nested calls in animation definitions (now supported)
animation complex = pulsating_animation(
source=shift_animation(
source=solid(color=red),
offset=triangle(min=0, max=29, period=3s)
),
period=2s
)
# Nested calls in run statements
sequence demo {
play pulsating_animation(source=shift_animation(source=solid(color=blue), offset=5), period=1s) for 10s
}
Error Handling
The DSL compiler validates classes and parameters at transpilation time, catching errors before execution:
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:
# 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:
# Error: Invalid parameter name
animation pulse = pulsating_animation(invalid_param=123)
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
# 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:
# 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:
# Error: Undefined color reference
animation pulse = pulsating_animation(color=undefined_color)
# Transpiler error: "Undefined reference: 'undefined_color'"
# Error: Undefined animation reference
run nonexistent_animation
# Transpiler error: "Undefined reference: 'nonexistent_animation'"
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 or constraint violations
- Reference validation: Using undefined colors, animations, or variables
- Type validation: Incorrect parameter types or incompatible assignments
- Runtime errors: Errors during Berry code execution (rare with good validation)
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
-
Compile once, run many times:
var compiled = animation_dsl.compile(dsl_source) var fn = compile(compiled) # Run multiple times without recompilation fn() # First execution fn() # Subsequent executions are faster -
Use programmatic API for performance-critical code:
# DSL for high-level structure animation_dsl.execute(''' sequence main { play performance_critical_anim for 10s } run main ''') # Programmatic for performance-critical animations var performance_critical_anim = animation.create_optimized_animation() -
Minimize DSL recompilation:
# Good: Compile once var runtime = animation_dsl.create_runtime() runtime.load_dsl(source) runtime.execute() # Avoid: Recompiling same DSL repeatedly # animation_dsl.execute(same_source) # Don't do this in loops
Integration Examples
With Tasmota Rules
# In autoexec.be
import animation
import animation_dsl
def handle_rule_trigger(event)
if event == "motion"
animation_dsl.execute("color alert = #FF0000\n"
"animation alert_anim = pulsating_animation(color=alert, period=500ms)\n"
"run alert_anim for 5s")
elif event == "door"
animation_dsl.execute("color welcome = #00FF00\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
# 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
-
Structure your DSL files:
# Strip configuration first strip length 60 # Colors next color red = #FF0000 color blue = #0000FF # 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 -
Use meaningful names:
# Good color warning_red = #FF0000 animation door_alert = pulsating_animation(color=warning_red, period=500ms) # Avoid color c1 = #FF0000 animation a1 = pulsating_animation(color=c1, period=500ms) -
Comment your DSL:
# Security system colors color normal_blue = #000080 # Idle state color alert_red = #FF0000 # Alert state color success_green = #00FF00 # 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 } -
Organize complex projects:
# 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.