Berry animation fix breathe (#24246)
* Remove tab from json * Berry animation fix breathe
This commit is contained in:
parent
f1e519af14
commit
a9d32dc979
@ -1,226 +1,179 @@
|
||||
# Berry Animation Framework
|
||||
|
||||
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using a simple Domain-Specific Language (DSL).
|
||||
!!! note "Requires `#define USE_BERRY_ANIMATION`, included in Tasmota32"
|
||||
|
||||
## ✨ Features
|
||||
A lightweight animation framework for controlling addressable LED strips (WS2812, SK6812, etc.) on Tasmota-based ESP32 devices.
|
||||
|
||||
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, sparkle, wave, and more
|
||||
- **🌈 Advanced Color System** - Predefined palettes, custom gradients, smooth color cycling
|
||||
- **📝 Simple DSL Syntax** - Write animations in intuitive, declarative language
|
||||
- **⚡ High Performance** - Optimized for embedded systems with minimal memory usage
|
||||
- **🔧 Extensible** - Create custom animations and effects
|
||||
- **🎯 Position-Based Effects** - Precise control over individual LED positions
|
||||
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with oscillating values
|
||||
- **🎭 Sequences** - Create complex shows with timing and loops
|
||||
## Why a DSL?
|
||||
|
||||
## 🚀 Quick Start
|
||||
Writing LED animations in pure Berry code requires understanding the animation engine internals, managing timing loops, and handling frame buffers manually. Animations are inherently asynchronous - they run over time while other code executes - making them difficult to manage with traditional state machines. The Animation DSL (Domain-Specific Language) simplifies this dramatically:
|
||||
|
||||
### Simple Pulsing Animation
|
||||
- **Declarative, not imperative** - Just specify *what* you want and *how long* it should take; the framework handles all intermediate states and timing
|
||||
- **No state machines** - Forget about tracking animation phases, transitions, and timing manually
|
||||
- **Readable syntax** - Write `animation pulse = pulsating_animation(color=red, period=2s)` instead of dozens of lines of Berry code
|
||||
- **Automatic engine management** - No need to create engines, manage frame buffers, or handle timing
|
||||
- **Built-in effects** - Access to pulse, breathe, fire, comet, sparkle, wave, and many more effects
|
||||
- **Dynamic parameters** - Oscillating values (sine, triangle, smooth) without manual math
|
||||
- **Sequences and templates** - Orchestrate complex shows with simple syntax
|
||||
|
||||
The DSL **transpiles to standard Berry code**, so you get the best of both worlds: easy authoring and full Berry compatibility. You can inspect the generated code, learn from it, or use it directly on devices without DSL support.
|
||||
|
||||
## Online Emulator
|
||||
|
||||
Test and create animations without a Tasmota device using the online emulator:
|
||||
|
||||
**[https://tasmota.github.io/docs/Tasmota-Berry-emulator/](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html)**
|
||||
|
||||
The emulator runs **entirely in your browser** with no server required. It includes:
|
||||
|
||||
- A complete Berry interpreter compiled to WebAssembly
|
||||
- Minimal Tasmota device emulation (LED strips, GPIO, timing)
|
||||
- Real-time LED strip visualization
|
||||
- DSL editor with syntax highlighting
|
||||
|
||||
Once your animation works in the emulator, copy the transpiled Berry code to your Tasmota device - it runs identically.
|
||||
|
||||
## Firmware Options
|
||||
|
||||
Option|Description
|
||||
:---|:---
|
||||
`#define USE_BERRY_ANIMATION`|Core animation framework (required)
|
||||
`#define USE_BERRY_ANIMATION_DSL`|Optional: DSL compiler with simplified Web UI for on-device animation editing
|
||||
|
||||
Without `USE_BERRY_ANIMATION_DSL`, use the online emulator to create animations and deploy the compiled Berry code.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Simple Breathing Animation
|
||||
|
||||
```berry
|
||||
# Define colors
|
||||
color bordeaux = 0x6F2C4F
|
||||
|
||||
# Create pulsing animation
|
||||
animation pulse_bordeaux = pulsating_animation(color=bordeaux, period=3s)
|
||||
|
||||
# Run it
|
||||
run pulse_bordeaux
|
||||
animation pulse = breathe_animation(color=red, period=2s)
|
||||
run pulse
|
||||
```
|
||||
|
||||
### Rainbow Color Cycling
|
||||
### Rainbow Smooth Color Cycling
|
||||
|
||||
```berry
|
||||
# Use predefined rainbow palette
|
||||
animation rainbow_cycle = rich_palette(
|
||||
palette=PALETTE_RAINBOW
|
||||
cycle_period=5s
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run rainbow_cycle
|
||||
animation rainbow = rich_palette_animation(palette=PALETTE_RAINBOW)
|
||||
run rainbow
|
||||
```
|
||||
|
||||
### Custom Color Palette
|
||||
### Animation Sequence
|
||||
|
||||
```berry
|
||||
# Define a sunset palette
|
||||
animation red_pulse = breathe_animation(color=red, period=2s)
|
||||
animation blue_pulse = breathe_animation(color=blue, period=1.5s)
|
||||
|
||||
sequence show repeat forever {
|
||||
play red_pulse for 4s
|
||||
wait 200ms
|
||||
play blue_pulse for 3s
|
||||
wait 300ms
|
||||
}
|
||||
run show
|
||||
```
|
||||
|
||||
## DSL Syntax Overview
|
||||
|
||||
### Numbers
|
||||
|
||||
#### Time Values
|
||||
|
||||
Time values require a unit suffix and are converted to milliseconds:
|
||||
|
||||
```berry
|
||||
500ms # Milliseconds (stays 500)
|
||||
2s # Seconds (converted to 2000ms)
|
||||
1m # Minutes (converted to 60000ms)
|
||||
```
|
||||
|
||||
#### Percentages
|
||||
|
||||
Percentages are converted to 0-255 range:
|
||||
|
||||
```berry
|
||||
0% # Converted to 0
|
||||
50% # Converted to 128
|
||||
100% # Converted to 255
|
||||
```
|
||||
|
||||
### Colors and Palettes
|
||||
|
||||
```berry
|
||||
# Hex colors
|
||||
color my_red = 0xFF0000
|
||||
|
||||
# Predefined: red, green, blue, white, yellow, orange, purple, cyan...
|
||||
|
||||
# Palettes for gradients
|
||||
palette sunset = [
|
||||
(0, 0x191970) # Midnight blue
|
||||
(64, purple) # Purple
|
||||
(128, 0xFF69B4) # Hot pink
|
||||
(192, orange) # Orange
|
||||
(255, yellow) # Yellow
|
||||
(0, navy), (128, purple), (255, orange)
|
||||
]
|
||||
|
||||
# Create palette animation
|
||||
animation sunset_glow = rich_palette(
|
||||
palette=sunset
|
||||
cycle_period=8s
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run sunset_glow
|
||||
# Built-in palettes: PALETTE_RAINBOW, PALETTE_FIRE...
|
||||
```
|
||||
|
||||
### Reusable Templates
|
||||
### Value Providers (Oscillators)
|
||||
|
||||
Create parameterized animation patterns that can be reused with different settings:
|
||||
Create dynamic values that change over time:
|
||||
|
||||
```berry
|
||||
# Define a reusable template
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
set breathing = smooth(min_value=50, max_value=255, duration=3s)
|
||||
animation pulse = solid(color=red)
|
||||
pulse.opacity = breathing
|
||||
```
|
||||
|
||||
Available: `smooth`, `triangle`, `sine_osc`, `linear`, `square`, `ease_in`, `ease_out`, `elastic`, `bounce`
|
||||
|
||||
### Template Animations
|
||||
|
||||
Create reusable, parameterized animation patterns:
|
||||
|
||||
```berry
|
||||
template animation pulse_effect {
|
||||
param pulse_color type color
|
||||
param speed
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
opacity=brightness
|
||||
)
|
||||
|
||||
animation pulse = pulsating_animation(color=pulse_color, period=speed)
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template with different parameters
|
||||
pulse_effect(red, 2s, 255) # Bright red pulse
|
||||
pulse_effect(blue, 1s, 150) # Dimmer blue pulse
|
||||
pulse_effect(0xFF69B4, 3s, 200) # Hot pink pulse
|
||||
```
|
||||
animation red_pulse = pulse_effect(color=red, speed=2s)
|
||||
animation blue_pulse = pulse_effect(color=blue, speed=1s)
|
||||
|
||||
### Animation 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
|
||||
}
|
||||
sequence show repeat forever {
|
||||
play red_pulse for 5s
|
||||
play blue_pulse for 5s
|
||||
}
|
||||
|
||||
run rgb_show
|
||||
run show
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
## Animation Reference
|
||||
|
||||
### Getting Started
|
||||
- **[Quick Start Guide](docs/QUICK_START.md)** - Get up and running in 5 minutes
|
||||
- **[DSL Reference](docs/DSL_REFERENCE.md)** - Complete DSL syntax and features
|
||||
- **[Examples](docs/EXAMPLES.md)** - Comprehensive examples and tutorials
|
||||
Animation|Description
|
||||
:---|:---
|
||||
`solid`|Solid color fill
|
||||
`pulsating_animation`|Breathing/pulsing effect with smooth transitions
|
||||
`breathe_animation`|Natural breathing effect with customizable curve
|
||||
`beacon_animation`|Pulse/highlight at specific position with optional slew
|
||||
`crenel_position_animation`|Crenel/square wave pattern
|
||||
`comet_animation`|Moving comet with fading tail
|
||||
`twinkle_animation`|Twinkling stars effect
|
||||
`fire_animation`|Realistic fire simulation
|
||||
`rich_palette`|Smooth palette color transitions
|
||||
`gradient_animation`|Linear or radial color gradients
|
||||
`palette_gradient_animation`|Gradient patterns with palette colors
|
||||
`palette_meter_animation`|Meter/bar patterns
|
||||
`gradient_meter_animation`|VU meter with gradient and peak hold
|
||||
`noise_animation`|Perlin noise patterns
|
||||
`wave_animation`|Wave motion effects
|
||||
|
||||
### Reference
|
||||
- **[Animation Class Hierarchy](docs/ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
|
||||
- **[Oscillation Patterns](docs/OSCILLATION_PATTERNS.md)** - Dynamic value patterns and waveforms
|
||||
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
## Documentation
|
||||
|
||||
### Advanced
|
||||
- **[User Functions](docs/USER_FUNCTIONS.md)** - Create custom animation functions
|
||||
- **[Animation Development](docs/ANIMATION_DEVELOPMENT.md)** - Create custom animations
|
||||
- **[Transpiler Architecture](docs/TRANSPILER_ARCHITECTURE.md)** - DSL transpiler internals and processing flow
|
||||
|
||||
## 🎯 Core Concepts
|
||||
|
||||
### DSL-First Design
|
||||
Write animations using simple, declarative syntax:
|
||||
```berry
|
||||
animation fire_effect = fire_animation(intensity=200, cooling_rate=55, sparking_rate=120)
|
||||
run fire_effect
|
||||
```
|
||||
|
||||
### Dynamic Parameters
|
||||
Use oscillating values to create complex effects:
|
||||
```berry
|
||||
animation pulsing_comet = comet_animation(
|
||||
color=red
|
||||
tail_length = smooth(min_value=5, max_value=15, duration=3s)
|
||||
speed=2
|
||||
)
|
||||
```
|
||||
|
||||
### Color Palettes
|
||||
Rich color transitions with predefined or custom palettes:
|
||||
```berry
|
||||
palette custom_palette = [(0, blue), (128, purple), (255, pink)]
|
||||
animation palette_cycle = rich_palette(palette=custom_palette, cycle_period=4s)
|
||||
```
|
||||
|
||||
## 🎨 Animation Types
|
||||
|
||||
### Basic Effects
|
||||
- **Pulse** - Breathing/pulsing effects with smooth transitions
|
||||
- **Sparkle** - Random twinkling and starfield effects
|
||||
- **Fire** - Realistic fire simulation with warm colors
|
||||
- **Comet** - Moving comet with customizable tail
|
||||
|
||||
### Color Animations
|
||||
- **Rich Palette** - Smooth color transitions using predefined palettes
|
||||
- **Color Cycling** - Custom color sequences with smooth blending
|
||||
- **Gradient** - Linear and radial color gradients
|
||||
- **Plasma** - Classic plasma effects with sine wave interference
|
||||
|
||||
### Pattern Effects
|
||||
- **Wave** - Mathematical waveforms (sine, triangle, square, sawtooth)
|
||||
- **Noise** - Organic patterns using Perlin noise
|
||||
- **Position-Based** - Precise control over individual LED positions
|
||||
|
||||
### Motion Effects
|
||||
- **Bounce** - Physics-based bouncing with gravity and damping
|
||||
- **Shift** - Scrolling and translation effects
|
||||
- **Scale** - Size transformation and breathing effects
|
||||
- **Jitter** - Add random variations to any animation
|
||||
|
||||
## 🔧 Installation
|
||||
|
||||
### Prerequisites
|
||||
- Tasmota firmware with Berry support
|
||||
- Addressable LED strip (WS2812, SK6812, etc.)
|
||||
|
||||
### Setup
|
||||
1. **Enable Berry** in Tasmota configuration
|
||||
2. **Configure LED strip** using Tasmota's LED configuration
|
||||
3. **Import the framework**:
|
||||
```berry
|
||||
import animation
|
||||
```
|
||||
4. **Create your first animation** using the DSL
|
||||
|
||||
## 🌈 Predefined Palettes
|
||||
|
||||
The framework includes several built-in color palettes:
|
||||
|
||||
- **PALETTE_RAINBOW** - Standard 7-color rainbow (Red → Orange → Yellow → Green → Blue → Indigo → Violet)
|
||||
- **PALETTE_RGB** - Simple RGB cycle (Red → Green → Blue)
|
||||
- **PALETTE_FIRE** - Warm fire colors (Black → Dark Red → Red → Orange → Yellow)
|
||||
- **PALETTE_SUNSET_TICKS** - Sunset colors (Orange Red → Dark Orange → Gold → Hot Pink → Purple → Midnight Blue)
|
||||
- **PALETTE_OCEAN** - Blue and green ocean tones (Navy → Blue → Cyan → Spring Green → Green)
|
||||
- **PALETTE_FOREST** - Various green forest tones (Dark Green → Forest Green → Lime Green → Mint Green → Light Green)
|
||||
|
||||
```berry
|
||||
# Use any predefined palette
|
||||
animation ocean_waves = rich_palette(
|
||||
palette=PALETTE_OCEAN
|
||||
cycle_period=8s
|
||||
transition_type=1
|
||||
)
|
||||
run ocean_waves
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
**Happy Animating!** 🎨✨
|
||||
- **[Quick Start Guide](berry_animation_docs/QUICK_START.md)** - Get running in 5 minutes
|
||||
- **[DSL Reference](berry_animation_docs/DSL_REFERENCE.md)** - Complete syntax reference
|
||||
- **[Examples](berry_animation_docs/EXAMPLES.md)** - Comprehensive examples
|
||||
- **[Animation Classes](berry_animation_docs/ANIMATION_CLASS_HIERARCHY.md)** - All animations and parameters
|
||||
- **[Oscillation Patterns](berry_animation_docs/OSCILLATION_PATTERNS.md)** - Dynamic value waveforms
|
||||
- **[User Functions](berry_animation_docs/USER_FUNCTIONS.md)** - Extend with custom Berry functions
|
||||
- **[Troubleshooting](berry_animation_docs/TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
|
||||
@ -694,10 +694,8 @@ SUCCESS
|
||||
|
||||
| Symbol | Type | Builtin | Dangerous | Takes Args |
|
||||
|--------------------------|-----------------------|---------|-----------|------------|
|
||||
| `PALETTE_FOREST` | palette_constant | ✓ | | |
|
||||
| `fire_anim` | animation | | | |
|
||||
| `fire_colors` | palette | | | |
|
||||
| `forest_anim` | animation | | | |
|
||||
| `ocean_anim` | animation | | | |
|
||||
| `ocean_colors` | palette | | | |
|
||||
| `palette_demo` | sequence | | | |
|
||||
|
||||
@ -29,9 +29,6 @@ fire_anim_.cycle_period = 5000
|
||||
var ocean_anim_ = animation.rich_palette_animation(engine)
|
||||
ocean_anim_.palette = ocean_colors_
|
||||
ocean_anim_.cycle_period = 8000
|
||||
var forest_anim_ = animation.rich_palette_animation(engine)
|
||||
forest_anim_.palette = animation.PALETTE_FOREST
|
||||
forest_anim_.cycle_period = 8000
|
||||
# Sequence to show both palettes
|
||||
var palette_demo_ = animation.sequence_manager(engine)
|
||||
.push_play_step(fire_anim_, 10000)
|
||||
@ -41,7 +38,6 @@ var palette_demo_ = animation.sequence_manager(engine)
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, 2)
|
||||
.push_play_step(fire_anim_, 3000)
|
||||
.push_play_step(ocean_anim_, 3000)
|
||||
.push_play_step(forest_anim_, 3000)
|
||||
)
|
||||
engine.add(palette_demo_)
|
||||
engine.run()
|
||||
@ -70,8 +66,6 @@ animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5
|
||||
|
||||
animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
|
||||
|
||||
animation forest_anim = rich_palette_animation(palette=PALETTE_FOREST, cycle_period=8s)
|
||||
|
||||
# Sequence to show both palettes
|
||||
sequence palette_demo {
|
||||
play fire_anim for 10s
|
||||
@ -81,7 +75,6 @@ sequence palette_demo {
|
||||
repeat 2 times {
|
||||
play fire_anim for 3s
|
||||
play ocean_anim for 3s
|
||||
play forest_anim for 3s
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,8 +20,6 @@ animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5
|
||||
|
||||
animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
|
||||
|
||||
animation forest_anim = rich_palette_animation(palette=PALETTE_FOREST, cycle_period=8s)
|
||||
|
||||
# Sequence to show both palettes
|
||||
sequence palette_demo {
|
||||
play fire_anim for 10s
|
||||
@ -31,7 +29,6 @@ sequence palette_demo {
|
||||
repeat 2 times {
|
||||
play fire_anim for 3s
|
||||
play ocean_anim for 3s
|
||||
play forest_anim for 3s
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Plain background
|
||||
# @desc Solid red background - the simplest animation
|
||||
|
||||
animation back = solid(color=red)
|
||||
run back
|
||||
@ -1,4 +1,4 @@
|
||||
# Rotation of colors in the background based on palette
|
||||
# @desc Rainbow colors cycling through the entire strip over time
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Transition of colors in the background based on palette
|
||||
# @desc Smooth color transitions using rich_palette with sine interpolation
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Pattern of colors in the background based on palette
|
||||
# @desc Rainbow gradient pattern across the LED strip
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Pattern of colors in the background based on palette, spatial period = 1/2 strip
|
||||
# @desc Rainbow gradient with 2 repetitions across the strip
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Pattern of colors in the background based on palette, spatial period oscillating
|
||||
# @desc Rainbow gradient with oscillating spatial period (breathing effect)
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Pattern of colors in the background based on palette, rotating over 5 s
|
||||
# @desc Rainbow gradient rotating along the strip over 5 seconds
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Vue-meter based on random data
|
||||
# @desc VU-meter style animation with green-yellow-red gradient at 85%
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette vue_meter_palette = [
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Vue-meter based on random data
|
||||
# @desc VU-meter with random level using custom Berry function
|
||||
|
||||
berry """
|
||||
# define a pseudo-random generator, returns value in range 0..255
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Sky
|
||||
# @desc Night sky with twinkling stars on dark blue background
|
||||
|
||||
# Dark blue background
|
||||
color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Template animation for Cylon like eye
|
||||
# @desc Cylon-style scanning eye using template with customizable color
|
||||
|
||||
template animation cylon_eye {
|
||||
param eye_color type color default red
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Template animation for Cylon like eye
|
||||
# @desc Bidirectional shutter effect with rainbow colors
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Template animation with flags
|
||||
# @desc Advanced shutter template with ascending/descending flags
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,695 @@
|
||||
# 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 == "palette" || 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.
|
||||
1695
lib/libesp32/berry_animation/berry_animation_docs/DSL_REFERENCE.md
Normal file
1695
lib/libesp32/berry_animation/berry_animation_docs/DSL_REFERENCE.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,798 @@
|
||||
# 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(palette=colors, cycle_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_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col_.cycle_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.
|
||||
479
lib/libesp32/berry_animation/berry_animation_docs/EXAMPLES.md
Normal file
479
lib/libesp32/berry_animation/berry_animation_docs/EXAMPLES.md
Normal file
@ -0,0 +1,479 @@
|
||||
# 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(palette=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(palette=eye_palette, cycle_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(palette=[red, yellow, green, blue], cycle_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
|
||||
palette rainbow = [
|
||||
(0, red), (42, orange), (85, yellow)
|
||||
(128, green), (170, blue), (213, purple), (255, red)
|
||||
]
|
||||
|
||||
# Create cycling rainbow color
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=rainbow
|
||||
cycle_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! 🎨✨
|
||||
@ -0,0 +1,263 @@
|
||||
# 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.
|
||||
257
lib/libesp32/berry_animation/berry_animation_docs/QUICK_START.md
Normal file
257
lib/libesp32/berry_animation/berry_animation_docs/QUICK_START.md
Normal file
@ -0,0 +1,257 @@
|
||||
# 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(
|
||||
palette=PALETTE_RAINBOW
|
||||
cycle_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(
|
||||
palette=sunset
|
||||
cycle_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(
|
||||
palette=PALETTE_FIRE
|
||||
cycle_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(palette=colors, cycle_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! 🎨✨
|
||||
@ -0,0 +1,858 @@
|
||||
# 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(palette=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.
|
||||
1238
lib/libesp32/berry_animation/berry_animation_docs/TROUBLESHOOTING.md
Normal file
1238
lib/libesp32/berry_animation/berry_animation_docs/TROUBLESHOOTING.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,677 @@
|
||||
# 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.palette = animation.PALETTE_FIRE
|
||||
color_provider.cycle_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.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_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 palette in DSL
|
||||
animation gradient_effect = rich_palette(
|
||||
palette=custom_palette(0xFF6B35, 5, 255)
|
||||
cycle_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.
|
||||
@ -16,8 +16,8 @@ class BreatheAnimation : animation.animation
|
||||
var breathe_provider # Internal breathe color provider
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
# Note: 'color' is inherited from Animation base class
|
||||
static var PARAMS = animation.enc_params({
|
||||
"base_color": {"default": 0xFFFFFFFF}, # The base color to breathe (32-bit ARGB value)
|
||||
"min_brightness": {"min": 0, "max": 255, "default": 0}, # Minimum brightness level (0-255)
|
||||
"max_brightness": {"min": 0, "max": 255, "default": 255}, # Maximum brightness level (0-255)
|
||||
"period": {"min": 100, "default": 3000}, # Time for one complete breathe cycle in milliseconds
|
||||
@ -36,15 +36,21 @@ class BreatheAnimation : animation.animation
|
||||
self.breathe_provider = animation.breathe_color(engine)
|
||||
|
||||
# Set the animation's color parameter to use the breathe provider
|
||||
self.color = self.breathe_provider
|
||||
self.values["color"] = self.breathe_provider
|
||||
end
|
||||
|
||||
# Handle parameter changes - propagate to internal breathe provider
|
||||
def on_param_changed(name, value)
|
||||
super(self).on_param_changed(name, value)
|
||||
# Propagate relevant parameters to the breathe provider
|
||||
if name == "base_color"
|
||||
self.breathe_provider.base_color = value
|
||||
if name == "color"
|
||||
# When color is set, update the breathe_provider's base_color
|
||||
# but keep the breathe_provider as the actual color source for rendering
|
||||
if type(value) == 'int'
|
||||
self.breathe_provider.base_color = value
|
||||
# Restore the breathe_provider as the color source (bypass on_param_changed)
|
||||
self.values["color"] = self.breathe_provider
|
||||
end
|
||||
elif name == "min_brightness"
|
||||
self.breathe_provider.min_brightness = value
|
||||
elif name == "max_brightness"
|
||||
@ -64,13 +70,6 @@ class BreatheAnimation : animation.animation
|
||||
# Call parent start method first
|
||||
super(self).start(start_time)
|
||||
|
||||
# # Synchronize the breathe provider with current parameters
|
||||
# self.breathe_provider.base_color = self.base_color
|
||||
# self.breathe_provider.min_brightness = self.min_brightness
|
||||
# self.breathe_provider.max_brightness = self.max_brightness
|
||||
# self.breathe_provider.duration = self.period
|
||||
# self.breathe_provider.curve_factor = self.curve_factor
|
||||
|
||||
# Start the breathe provider with the same time
|
||||
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
|
||||
self.breathe_provider.start(actual_start_time)
|
||||
@ -84,7 +83,7 @@ class BreatheAnimation : animation.animation
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"BreatheAnimation(base_color=0x{self.base_color :08x}, min_brightness={self.min_brightness}, max_brightness={self.max_brightness}, period={self.period}, curve_factor={self.curve_factor}, priority={self.priority}, running={self.is_running})"
|
||||
return f"BreatheAnimation(color=0x{self.breathe_provider.base_color :08x}, min_brightness={self.min_brightness}, max_brightness={self.max_brightness}, period={self.period}, curve_factor={self.curve_factor}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
@ -96,4 +95,4 @@ def pulsating_animation(engine)
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'breathe_animation': BreatheAnimation, 'pulsating_animation': pulsating_animation}
|
||||
return {'breathe_animation': BreatheAnimation, 'pulsating_animation': pulsating_animation}
|
||||
|
||||
@ -7,15 +7,15 @@
|
||||
# Define common palette constants (in VRGB format: Value, Red, Green, Blue)
|
||||
# These palettes are compatible with the RichPaletteColorProvider
|
||||
|
||||
# Standard rainbow palette (7 colors)
|
||||
# Standard rainbow palette (7 colors with roughly constant brightness)
|
||||
var PALETTE_RAINBOW = bytes(
|
||||
"00FF0000" # Red (value 0)
|
||||
"24FFA500" # Orange (value 36)
|
||||
"00FC0000" # Red (value 0)
|
||||
"24FF8000" # Orange (value 36)
|
||||
"49FFFF00" # Yellow (value 73)
|
||||
"6E00FF00" # Green (value 110)
|
||||
"920000FF" # Blue (value 146)
|
||||
"B74B0082" # Indigo (value 183)
|
||||
"DBEE82EE" # Violet (value 219)
|
||||
"9200FFFF" # Cyan (value 146)
|
||||
"B70080FF" # Blue (value 183)
|
||||
"DB8000FF" # Violet (value 219)
|
||||
"FFFF0000" # Red (value 255)
|
||||
)
|
||||
|
||||
@ -35,41 +35,9 @@ var PALETTE_FIRE = bytes(
|
||||
"FFFFFF00" # Yellow (value 255)
|
||||
)
|
||||
|
||||
# Sunset palette with tick-based timing (equal time intervals)
|
||||
var PALETTE_SUNSET_TICKS = bytes(
|
||||
"28FF4500" # Orange red (40 ticks)
|
||||
"28FF8C00" # Dark orange (40 ticks)
|
||||
"28FFD700" # Gold (40 ticks)
|
||||
"28FF69B4" # Hot pink (40 ticks)
|
||||
"28800080" # Purple (40 ticks)
|
||||
"28191970" # Midnight blue (40 ticks)
|
||||
"00000080" # Navy blue (0 ticks - end marker)
|
||||
)
|
||||
|
||||
# Ocean palette (blue/green tones)
|
||||
var PALETTE_OCEAN = bytes(
|
||||
"00000080" # Navy blue (value 0)
|
||||
"400000FF" # Blue (value 64)
|
||||
"8000FFFF" # Cyan (value 128)
|
||||
"C000FF80" # Spring green (value 192)
|
||||
"FF008000" # Green (value 255)
|
||||
)
|
||||
|
||||
# Forest palette (green tones)
|
||||
var PALETTE_FOREST = bytes(
|
||||
"00006400" # Dark green (value 0)
|
||||
"40228B22" # Forest green (value 64)
|
||||
"8032CD32" # Lime green (value 128)
|
||||
"C09AFF9A" # Mint green (value 192)
|
||||
"FF90EE90" # Light green (value 255)
|
||||
)
|
||||
|
||||
# Export all palettes
|
||||
return {
|
||||
"PALETTE_RAINBOW": PALETTE_RAINBOW,
|
||||
"PALETTE_RGB": PALETTE_RGB,
|
||||
"PALETTE_FIRE": PALETTE_FIRE,
|
||||
"PALETTE_SUNSET_TICKS": PALETTE_SUNSET_TICKS,
|
||||
"PALETTE_OCEAN": PALETTE_OCEAN,
|
||||
"PALETTE_FOREST": PALETTE_FOREST
|
||||
"PALETTE_FIRE": PALETTE_FIRE
|
||||
}
|
||||
@ -57,6 +57,9 @@ class FrameBufferNtv
|
||||
# Linear interpolation between two colors using explicit blend factor
|
||||
# Returns the blended color as a 32-bit integer (ARGB format - 0xAARRGGBB)
|
||||
#
|
||||
# This function matches the original berry_animate frame.blend(color1, color2, blend_factor) behavior
|
||||
# Used for creating smooth gradients like beacon slew regions
|
||||
#
|
||||
# color1: destination/background color (ARGB format - 0xAARRGGBB)
|
||||
# color2: source/foreground color (ARGB format - 0xAARRGGBB)
|
||||
# blend_factor: blend factor (0-255 integer)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@ var anim = animation.breathe_animation(engine)
|
||||
print("Created breathe animation with defaults")
|
||||
|
||||
# Test default values
|
||||
print(f"Default base_color: 0x{anim.base_color :08x}")
|
||||
print(f"Default color: 0x{anim.breathe_provider.base_color :08x}")
|
||||
print(f"Default min_brightness: {anim.min_brightness}")
|
||||
print(f"Default max_brightness: {anim.max_brightness}")
|
||||
print(f"Default period: {anim.period}")
|
||||
@ -29,13 +29,13 @@ print(f"Default curve_factor: {anim.curve_factor}")
|
||||
|
||||
# Create another breathe animation and set custom parameters using virtual member assignment
|
||||
var blue_breathe = animation.breathe_animation(engine)
|
||||
blue_breathe.base_color = 0xFF0000FF
|
||||
blue_breathe.color = 0xFF0000FF
|
||||
blue_breathe.min_brightness = 20
|
||||
blue_breathe.max_brightness = 200
|
||||
blue_breathe.period = 4000
|
||||
blue_breathe.curve_factor = 3
|
||||
blue_breathe.priority = 15
|
||||
print(f"Blue breathe animation base_color: 0x{blue_breathe.base_color :08x}")
|
||||
print(f"Blue breathe animation color: 0x{blue_breathe.breathe_provider.base_color :08x}")
|
||||
print(f"Blue breathe animation min_brightness: {blue_breathe.min_brightness}")
|
||||
print(f"Blue breathe animation max_brightness: {blue_breathe.max_brightness}")
|
||||
print(f"Blue breathe animation period: {blue_breathe.period}")
|
||||
@ -43,12 +43,12 @@ print(f"Blue breathe animation curve_factor: {blue_breathe.curve_factor}")
|
||||
|
||||
# Create red breathe animation with different parameters
|
||||
var red_breathe = animation.breathe_animation(engine)
|
||||
red_breathe.base_color = 0xFFFF0000
|
||||
red_breathe.color = 0xFFFF0000
|
||||
red_breathe.min_brightness = 10
|
||||
red_breathe.max_brightness = 180
|
||||
red_breathe.period = 3000
|
||||
red_breathe.curve_factor = 2
|
||||
print(f"Red breathe animation base_color: 0x{red_breathe.base_color :08x}")
|
||||
print(f"Red breathe animation color: 0x{red_breathe.breathe_provider.base_color :08x}")
|
||||
|
||||
# Test parameter updates using virtual member assignment
|
||||
blue_breathe.min_brightness = 30
|
||||
@ -133,7 +133,7 @@ print("✓ Animation added to engine successfully")
|
||||
assert(anim != nil, "Default breathe animation should be created")
|
||||
assert(blue_breathe != nil, "Custom breathe animation should be created")
|
||||
assert(red_breathe != nil, "Red breathe animation should be created")
|
||||
assert(blue_breathe.base_color == 0xFF0000FF, "Blue breathe should have correct base_color")
|
||||
assert(blue_breathe.breathe_provider.base_color == 0xFF0000FF, "Blue breathe should have correct color")
|
||||
assert(blue_breathe.min_brightness == 30, "Min brightness should be updated to 30")
|
||||
assert(blue_breathe.max_brightness == 220, "Max brightness should be updated to 220")
|
||||
assert(blue_breathe.period == 3500, "Breathe period should be updated to 3500")
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
# Test file for Leds JavaScript bridge functions
|
||||
#
|
||||
# This file contains tests for the JavaScript-specific Leds display functions
|
||||
|
||||
print("Testing Leds JavaScript bridge functions...")
|
||||
|
||||
# Create a Leds strip with 10 pixels
|
||||
var strip = Leds(10)
|
||||
assert(strip.leds == 10, "LED strip should have 10 pixels")
|
||||
|
||||
# Test show method (should not error even if not in browser)
|
||||
print("Testing show method...")
|
||||
strip.clear_to(0xFF0000) # Fill with red
|
||||
strip.show() # Should not error
|
||||
print("show method works (no error)")
|
||||
|
||||
# Test pixel_count method (should return leds when not in browser)
|
||||
print("Testing pixel_count method...")
|
||||
var pixel_count = strip.pixel_count()
|
||||
assert(pixel_count == 10, f"Pixel count should be 10 when not in browser, got {pixel_count}")
|
||||
print("pixel_count method works (returns 10 when not in browser)")
|
||||
|
||||
# Test that show works with different pixel patterns
|
||||
print("Testing show with different patterns...")
|
||||
|
||||
# Pattern 1: All red
|
||||
strip.clear_to(0xFF0000)
|
||||
strip.show()
|
||||
print("Pattern 1 (all red) sent to JS")
|
||||
|
||||
# Pattern 2: Mixed colors
|
||||
strip.clear_to(0x000000)
|
||||
strip.set_pixel_color(0, 0xFF0000) # Red
|
||||
strip.set_pixel_color(1, 0x00FF00) # Green
|
||||
strip.set_pixel_color(2, 0x0000FF) # Blue
|
||||
strip.set_pixel_color(3, 0xFFFF00) # Yellow
|
||||
strip.set_pixel_color(4, 0x00FFFF) # Cyan
|
||||
strip.show()
|
||||
print("Pattern 2 (mixed colors) sent to JS")
|
||||
|
||||
# Pattern 3: Gradient-like pattern
|
||||
strip.clear_to(0x000000)
|
||||
var i = 0
|
||||
while i < strip.leds
|
||||
var brightness = (i * 255) / strip.leds
|
||||
strip.set_pixel_color(i, (brightness << 16)) # Red gradient
|
||||
i += 1
|
||||
end
|
||||
strip.show()
|
||||
print("Pattern 3 (gradient) sent to JS")
|
||||
|
||||
print("All Leds JavaScript bridge tests passed!")
|
||||
return true
|
||||
@ -21,7 +21,7 @@ var anim = animation.pulsating_animation(engine)
|
||||
print("Created pulse animation with defaults")
|
||||
|
||||
# Test default values
|
||||
print(f"Default base_color: 0x{anim.base_color :08x}")
|
||||
print(f"Default color: 0x{anim.breathe_provider.base_color :08x}")
|
||||
print(f"Default min_brightness: {anim.min_brightness}")
|
||||
print(f"Default max_brightness: {anim.max_brightness}")
|
||||
print(f"Default period: {anim.period}")
|
||||
@ -29,11 +29,11 @@ print(f"Default curve_factor: {anim.curve_factor}") # Should be 1 for pulsating
|
||||
|
||||
# Test with custom parameters using virtual member assignment
|
||||
var blue_pulse = animation.pulsating_animation(engine)
|
||||
blue_pulse.base_color = 0xFF0000FF
|
||||
blue_pulse.color = 0xFF0000FF
|
||||
blue_pulse.min_brightness = 50
|
||||
blue_pulse.max_brightness = 200
|
||||
blue_pulse.period = 2000
|
||||
print(f"Blue pulse animation base_color: 0x{blue_pulse.base_color :08x}")
|
||||
print(f"Blue pulse animation color: 0x{blue_pulse.breathe_provider.base_color :08x}")
|
||||
print(f"Blue pulse animation min_brightness: {blue_pulse.min_brightness}")
|
||||
print(f"Blue pulse animation max_brightness: {blue_pulse.max_brightness}")
|
||||
print(f"Blue pulse animation period: {blue_pulse.period}")
|
||||
@ -41,11 +41,11 @@ print(f"Blue pulse animation curve_factor: {blue_pulse.curve_factor}")
|
||||
|
||||
# Test creating another pulse with different parameters
|
||||
var red_pulse = animation.pulsating_animation(engine)
|
||||
red_pulse.base_color = 0xFFFF0000 # Red color
|
||||
red_pulse.color = 0xFFFF0000 # Red color
|
||||
red_pulse.min_brightness = 20
|
||||
red_pulse.max_brightness = 180
|
||||
red_pulse.period = 1500
|
||||
print(f"Red pulse animation base_color: 0x{red_pulse.base_color :08x}")
|
||||
print(f"Red pulse animation color: 0x{red_pulse.breathe_provider.base_color :08x}")
|
||||
|
||||
# Test parameter updates using virtual member assignment
|
||||
blue_pulse.min_brightness = 30
|
||||
@ -104,7 +104,7 @@ print(f"First pixel after rendering: 0x{frame.get_pixel_color(0) :08x}")
|
||||
# Validate key test results
|
||||
assert(anim != nil, "Default pulse animation should be created")
|
||||
assert(blue_pulse != nil, "Custom pulse animation should be created")
|
||||
assert(blue_pulse.base_color == 0xFF0000FF, "Blue pulse should have correct base_color")
|
||||
assert(blue_pulse.breathe_provider.base_color == 0xFF0000FF, "Blue pulse should have correct color")
|
||||
assert(blue_pulse.min_brightness == 30, "Min brightness should be updated to 30")
|
||||
assert(blue_pulse.max_brightness == 220, "Max brightness should be updated to 220")
|
||||
assert(blue_pulse.period == 1800, "Pulse period should be updated to 1800")
|
||||
|
||||
@ -47,6 +47,7 @@ def run_all_tests()
|
||||
|
||||
# Core framework tests
|
||||
"lib/libesp32/berry_animation/src/tests/frame_buffer_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/frame_buffer_js_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/constraint_encoding_test.be", # Tests parameter constraint encoding/decoding
|
||||
"lib/libesp32/berry_animation/src/tests/nillable_parameter_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/parameterized_object_test.be", # Tests parameter management base class
|
||||
|
||||
@ -132,10 +132,14 @@ while i < size(density_test_cases)
|
||||
density_time += 167
|
||||
density_twinkle.update(density_time)
|
||||
|
||||
# Count active twinkles by checking alpha channel in current_colors buffer
|
||||
var active_count = 0
|
||||
var strip_len = engine.strip_length
|
||||
var j = 0
|
||||
while j < size(density_twinkle.twinkle_states)
|
||||
if density_twinkle.twinkle_states[j] > 0
|
||||
while j < strip_len
|
||||
var color = density_twinkle.current_colors.get(j * 4, -4)
|
||||
var alpha = (color >> 24) & 0xFF
|
||||
if alpha > 0
|
||||
active_count += 1
|
||||
end
|
||||
j += 1
|
||||
@ -278,16 +282,18 @@ end
|
||||
|
||||
# Test 10: Internal State Inspection
|
||||
print("\n10. Testing internal state...")
|
||||
print(f"Twinkle states array size: {size(twinkle.twinkle_states)}")
|
||||
print(f"Current colors array size: {size(twinkle.current_colors)}")
|
||||
var strip_len = engine.strip_length
|
||||
print(f"Current colors buffer size: {size(twinkle.current_colors)} bytes ({size(twinkle.current_colors) / 4} pixels)")
|
||||
print(f"Random seed: {twinkle.random_seed}")
|
||||
print(f"Last update time: {twinkle.last_update}")
|
||||
|
||||
# Check some internal states
|
||||
# Check some internal states by examining alpha channel in current_colors
|
||||
var active_twinkles = 0
|
||||
i = 0
|
||||
while i < size(twinkle.twinkle_states)
|
||||
if twinkle.twinkle_states[i] > 0
|
||||
while i < strip_len
|
||||
var color = twinkle.current_colors.get(i * 4, -4)
|
||||
var alpha = (color >> 24) & 0xFF
|
||||
if alpha > 0
|
||||
active_twinkles += 1
|
||||
end
|
||||
i += 1
|
||||
@ -430,8 +436,9 @@ var new_stars_found = 0
|
||||
var full_brightness_stars = 0
|
||||
|
||||
var k = 0
|
||||
while k < size(alpha_test_twinkle.current_colors) && k < 10 # Check first 10 pixels
|
||||
var color = alpha_test_twinkle.current_colors[k]
|
||||
var num_pixels = size(alpha_test_twinkle.current_colors) / 4 # 4 bytes per pixel
|
||||
while k < num_pixels && k < 10 # Check first 10 pixels
|
||||
var color = alpha_test_twinkle.current_colors.get(k * 4, -4)
|
||||
var alpha = (color >> 24) & 0xFF
|
||||
var red = (color >> 16) & 0xFF
|
||||
var green = (color >> 8) & 0xFF
|
||||
@ -463,9 +470,8 @@ fade_twinkle.twinkle_speed = 6
|
||||
fade_twinkle.fade_speed = 100 # Medium fade
|
||||
fade_twinkle.start()
|
||||
|
||||
# Manually create a star at full alpha
|
||||
fade_twinkle.twinkle_states[5] = 1 # Mark as active
|
||||
fade_twinkle.current_colors[5] = 0xFFFFFFFF # Full white at full alpha
|
||||
# Manually create a star at full alpha (alpha channel serves as active state)
|
||||
fade_twinkle.current_colors.set(5 * 4, 0xFFFFFFFF, -4) # Full white at full alpha
|
||||
|
||||
# Track alpha over several fade cycles
|
||||
var fade_history = []
|
||||
@ -476,7 +482,7 @@ while fade_cycle < 5
|
||||
fade_test_time += 167 # ~6Hz updates
|
||||
fade_twinkle.update(fade_test_time)
|
||||
|
||||
var color = fade_twinkle.current_colors[5]
|
||||
var color = fade_twinkle.current_colors.get(5 * 4, -4)
|
||||
var alpha = (color >> 24) & 0xFF
|
||||
var red = (color >> 16) & 0xFF
|
||||
var green = (color >> 8) & 0xFF
|
||||
@ -519,16 +525,16 @@ reset_twinkle.fade_speed = 255 # Max fade speed
|
||||
reset_twinkle.start()
|
||||
|
||||
# Create a star with very low alpha (should disappear quickly)
|
||||
reset_twinkle.twinkle_states[3] = 1
|
||||
reset_twinkle.current_colors[3] = 0x0500FF00 # Very low alpha (5), full green
|
||||
# Alpha channel serves as active state - alpha > 0 means active
|
||||
reset_twinkle.current_colors.set(3 * 4, 0x0500FF00, -4) # Very low alpha (5), full green
|
||||
|
||||
# Update once (should reset to transparent)
|
||||
reset_twinkle.update(17000)
|
||||
|
||||
var final_color = reset_twinkle.current_colors[3]
|
||||
var final_state = reset_twinkle.twinkle_states[3]
|
||||
var final_color = reset_twinkle.current_colors.get(3 * 4, -4)
|
||||
var final_alpha = (final_color >> 24) & 0xFF
|
||||
|
||||
if final_color == 0x00000000 && final_state == 0
|
||||
if final_color == 0x00000000 && final_alpha == 0
|
||||
print("✅ Star correctly reset to transparent when alpha reached zero")
|
||||
else
|
||||
print("❌ Star not properly reset")
|
||||
@ -549,7 +555,7 @@ zero_density_twinkle.update(18000)
|
||||
print("Zero density twinkle created and updated")
|
||||
|
||||
# Test that transparency is working by checking the alpha-based fading results from previous test
|
||||
var transparency_working = (final_color == 0x00000000 && final_state == 0)
|
||||
var transparency_working = (final_color == 0x00000000 && final_alpha == 0)
|
||||
var alpha_preserved = alpha_decreased
|
||||
var background_preserved = 10 # Assume good based on previous alpha tests
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user