Berry update of preview of animation framework (#23816)

This commit is contained in:
s-hadinger 2025-08-22 19:44:35 +02:00 committed by GitHub
parent b5775895d2
commit 772ba227e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
202 changed files with 47248 additions and 50018 deletions

View File

@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- Epdiy library from v1.0.0 to v2.0.0
- ESP8266 platform update from 2025.07.00 to 2025.08.00 (#23801)
- Support for ESP32-C5 (#23804)
- Berry update of preview of animation framework
### Fixed
- Syslog RFC5424 compliance (#23509)

View File

@ -86,6 +86,9 @@ be_extern_native_module(haspmota);
#ifdef USE_WS2812
#ifdef USE_BERRY_ANIMATION
be_extern_native_module(animation);
#ifdef USE_BERRY_ANIMATION_DSL
be_extern_native_module(animation_dsl);
#endif // USE_BERRY_ANIMATION_DSL
#endif // USE_BERRY_ANIMATION
#endif // USE_WS2812
be_extern_native_module(matter);
@ -225,6 +228,9 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = {
#ifdef USE_WS2812
#ifdef USE_BERRY_ANIMATION
&be_native_module(animation),
#ifdef USE_BERRY_ANIMATION_DSL
&be_native_module(animation_dsl),
#endif // USE_BERRY_ANIMATION_DSL
#endif // USE_BERRY_ANIMATION
#endif // USE_WS2812
#endif // TASMOTA

View File

@ -178,6 +178,9 @@ static bmapnode* insert(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
static bmapnode* find(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
{
if (map->size == 0) { /* this situation happens only for solidified empty maps that are compacted */
return NULL;
}
bmapnode *slot = hash2slot(map, hash);
if (isnil(slot)) {
return NULL;

View File

@ -1,215 +1,221 @@
# Tasmota Berry Animation Framework
# Berry Animation Framework
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using Berry scripting language.
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using a simple Domain-Specific Language (DSL).
## ✨ Features
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, twinkle, and more
- **🌈 Advanced Color System** - Palettes, gradients, color cycling with smooth transitions
- **📝 Domain-Specific Language (DSL)** - Write animations in intuitive, declarative syntax
- **🎨 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 user-defined functions
- **🔧 Extensible** - Create custom animations and effects
- **🎯 Position-Based Effects** - Precise control over individual LED positions
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with value providers
- **🎭 Event System** - Responsive animations that react to button presses, timers, and sensors
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with oscillating values
- **🎭 Sequences** - Create complex shows with timing and loops
## 🚀 Quick Start
### 1. Basic Berry Animation
```berry
import animation
# Create LED strip (60 LEDs)
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Create a pulsing red animation
var pulse_red = animation.pulse(
animation.solid(0xFFFF0000), # Red color
2000, # 2 second period
50, # Min brightness (0-255)
255 # Max brightness (0-255)
)
# Start the animation
engine.add_animation(pulse_red)
engine.start()
```
### 2. Using the Animation DSL
Create a file `my_animation.anim`:
### Simple Pulsing Animation
```dsl
# Define colors
color red = #FF0000
color blue = #0000FF
# Create animations
animation pulse_red = pulse(solid(red), 2s, 20%, 100%)
animation pulse_blue = pulse(solid(blue), 3s, 30%, 100%)
# Create pulsing animation
animation pulse_red = pulsating_animation(color=red, period=3s)
# Create a sequence
sequence demo {
play pulse_red for 5s
wait 1s
play pulse_blue for 5s
repeat 3 times:
play pulse_red for 2s
play pulse_blue for 2s
}
run demo
# Run it
run pulse_red
```
Load and run the DSL:
```berry
import animation
var strip = Leds(60)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
runtime.load_dsl_file("my_animation.anim")
```
### 3. Palette-Based Animations
### Rainbow Color Cycling
```dsl
# Define a fire palette
palette fire_colors = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF8000), # Orange
(255, #FFFF00) # Yellow
# Use predefined rainbow palette
animation rainbow_cycle = rich_palette(
palette=PALETTE_RAINBOW,
cycle_period=5s,
transition_type=1
)
run rainbow_cycle
```
### Custom Color Palette
```dsl
# Define a sunset palette
palette sunset = [
(0, #191970), # Midnight blue
(64, purple), # Purple
(128, #FF69B4), # Hot pink
(192, orange), # Orange
(255, yellow) # Yellow
]
# Create fire animation
animation fire_effect = rich_palette_animation(fire_colors, 3s, smooth, 255)
# Create palette animation
animation sunset_glow = rich_palette(
palette=sunset,
cycle_period=8s,
transition_type=1
)
run fire_effect
run sunset_glow
```
### Animation Sequences
```dsl
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
```
## 📚 Documentation
### User Guides
### Getting Started
- **[Quick Start Guide](docs/QUICK_START.md)** - Get up and running in 5 minutes
- **[API Reference](docs/API_REFERENCE.md)** - Complete Berry API documentation
- **[Examples](docs/EXAMPLES.md)** - Curated examples with explanations
- **[DSL Reference](docs/DSL_REFERENCE.md)** - Complete DSL syntax and features
- **[Examples](docs/EXAMPLES.md)** - Comprehensive examples and tutorials
### 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
### DSL (Domain-Specific Language)
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax guide
- **[DSL Grammar](.kiro/specs/berry-animation-framework/dsl-grammar.md)** - Formal grammar specification
- **[Palette Guide](.kiro/specs/berry-animation-framework/palette-quick-reference.md)** - Working with color palettes
### Advanced Topics
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom animation functions
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Responsive, interactive animations
- **[Project Structure](docs/PROJECT_STRUCTURE.md)** - Navigate the codebase
### Framework Design
- **[Requirements](.kiro/specs/berry-animation-framework/requirements.md)** - Project goals and requirements (✅ Complete)
- **[Architecture](.kiro/specs/berry-animation-framework/design.md)** - Framework design and architecture
- **[Future Features](.kiro/specs/berry-animation-framework/future_features.md)** - Planned enhancements
### Advanced
- **[User Functions](docs/USER_FUNCTIONS.md)** - Create custom animation functions
- **[Animation Development](docs/ANIMATION_DEVELOPMENT.md)** - Create custom animations
## 🎯 Core Concepts
### Unified Architecture
The framework uses a **unified pattern-animation architecture** where `Animation` extends `Pattern`. This means:
- Animations ARE patterns with temporal behavior
- Infinite composition: animations can use other animations as base patterns
- Consistent API across all visual elements
### DSL-First Design
Write animations using simple, declarative syntax:
```dsl
animation fire_effect = fire_animation(intensity=200, cooling=55, sparking=120)
run fire_effect
```
### Animation Engine
The `AnimationEngine` is the heart of the framework:
- Manages multiple animations with priority-based layering
- Handles timing, blending, and LED output
- Integrates with Tasmota's `fast_loop` for smooth performance
### Dynamic Parameters
Use oscillating values to create complex effects:
```dsl
animation pulsing_comet = comet_animation(
color=red,
tail_length=smooth_oscillator(5, 15, 3s),
speed=2
)
```
### Value Providers
Dynamic parameters that change over time:
- **Static values**: `solid(red)`
- **Oscillators**: `pulse(solid(red), smooth(50, 255, 2s))`
- **Color providers**: `rich_palette_animation(fire_palette, 3s)`
### Color Palettes
Rich color transitions with predefined or custom palettes:
```dsl
palette custom = [(0, blue), (128, purple), (255, pink)]
animation palette_cycle = rich_palette(palette=custom, cycle_period=4s)
```
## 🎨 Animation Types
### Basic Animations
- **`solid(color)`** - Static color fill
- **`pulse(pattern, period, min, max)`** - Pulsing brightness
- **`breathe(color, period)`** - Smooth breathing effect
### 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
### Pattern-Based Animations
- **`rich_palette_animation(palette, period, easing, brightness)`** - Palette color cycling
- **`gradient(color1, color2, ...)`** - Color gradients
- **`fire_animation(intensity, speed)`** - Realistic fire simulation
### 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
### Position-Based Animations
- **`pulse_position_animation(color, pos, size, fade)`** - Localized pulse
- **`comet_animation(color, tail_length, speed)`** - Moving comet effect
- **`twinkle_animation(color, density, speed)`** - Twinkling stars
### 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
- **`shift_left(pattern, speed)`** - Move pattern left
- **`shift_right(pattern, speed)`** - Move pattern right
- **`bounce(pattern, period)`** - Bouncing motion
- **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
### For Tasmota Development
1. Copy the `lib/libesp32/berry_animation/` directory to your Tasmota build
2. The framework will be available as the `animation` module
3. Use `import animation` in your Berry scripts
### Prerequisites
- Tasmota firmware with Berry support
- Addressable LED strip (WS2812, SK6812, etc.)
### For Testing/Development
1. Install Berry interpreter with Tasmota extensions
2. Set module path: `berry -m lib/libesp32/berry_animation`
3. Run examples: `berry examples/simple_engine_test.be`
### 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
## 🧪 Examples
## 🌈 Predefined Palettes
The framework includes comprehensive examples:
The framework includes several built-in color palettes:
### Berry Examples
- **[Basic Engine](lib/libesp32/berry_animation/examples/simple_engine_test.be)** - Simple animation setup
- **[Color Providers](lib/libesp32/berry_animation/examples/color_provider_demo.be)** - Dynamic color effects
- **[Position Effects](lib/libesp32/berry_animation/examples/pulse_position_animation_demo.be)** - Localized animations
- **[Event System](lib/libesp32/berry_animation/examples/event_system_demo.be)** - Interactive animations
- **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)
### DSL Examples
- **[Aurora Borealis](anim_examples/aurora_borealis.anim)** - Northern lights effect
- **[Breathing Colors](anim_examples/breathing_colors.anim)** - Smooth color breathing
- **[Fire Effect](anim_examples/fire_demo.anim)** - Realistic fire simulation
```dsl
# Use any predefined palette
animation ocean_waves = rich_palette(
palette=PALETTE_OCEAN,
cycle_period=8s,
transition_type=1
)
run ocean_waves
```
## ⚡ Performance
Optimized for embedded systems:
- **Memory Efficient** - Minimal RAM usage
- **CPU Optimized** - Efficient rendering algorithms
- **Scalable** - Handles strips from 10 to 1000+ LEDs
- **Real-time** - Smooth 30+ FPS animation
### Performance Tips
- Use longer animation periods (2-5 seconds) for smoother performance
- Limit simultaneous animations (3-5 max recommended)
- Consider strip length when designing complex effects
## 🤝 Contributing
### Running Tests
```bash
# Run all tests
berry lib/libesp32/berry_animation/tests/test_all.be
# Run specific test
berry lib/libesp32/berry_animation/tests/animation_engine_test.be
```
### Code Style
- Follow Berry language conventions
- Use descriptive variable names
- Include comprehensive comments
- Add test coverage for new features
Contributions are welcome! Areas for contribution:
- New animation effects
- Performance optimizations
- Documentation improvements
- Example animations
- Bug fixes and testing
## 📄 License
This project is part of the Tasmota ecosystem and follows the same licensing terms.
## 🙏 Acknowledgments
- **Tasmota Team** - For the excellent IoT platform
- **Berry Language** - For the lightweight scripting language
- **Community Contributors** - For testing, feedback, and improvements
This project is licensed under the MIT License.
---
**Ready to create amazing LED animations?** Start with the [Quick Start Guide](docs/QUICK_START.md)!
**Happy Animating!** 🎨✨

View File

@ -23,10 +23,10 @@ palette aurora_purple = [
# Base aurora animation with slow flowing colors
animation aurora_base = rich_palette_animation(
aurora_colors, # palette
10s, # cycle period
smooth, # transition type (explicit for clarity)
180 # brightness (dimmed for aurora effect)
palette=aurora_colors, # palette
cycle_period=10s, # cycle period
transition_type=SINE, # transition type (explicit for clarity)
brightness=180 # brightness (dimmed for aurora effect)
)
sequence demo {

View File

@ -1,7 +1,7 @@
# Breathing Colors - Slow color breathing effect
# Gentle pulsing through different colors
strip length 60
#strip length 60
# Define breathing colors
color breathe_red = 0xFF0000
@ -21,21 +21,21 @@ palette breathe_palette = [
]
# Create a rich palette color provider
pattern palette_pattern = rich_palette_animation(
breathe_palette, # palette
15s # cycle period (defaults: smooth transition, 255 brightness)
color palette_pattern = rich_palette(
palette=breathe_palette, # palette
cycle_period=15s # cycle period (defaults: smooth transition, 255 brightness)
)
# Create breathing animation using the palette
animation breathing = breathe(
palette_pattern, # base pattern
100, # min brightness
255, # max brightness
4s # breathing period (4 seconds)
animation breathing = breathe_animation(
color=palette_pattern, # base animation
min_brightness=100, # min brightness
max_brightness=255, # max brightness
period=4s # breathing period (4 seconds)
)
# Add gentle opacity breathing
breathing.opacity = smooth(100, 255, 4s)
breathing.opacity = smooth(min_value=100, max_value=255, duration=4s)
# Start the animation
run breathing

View File

@ -1,37 +1,37 @@
# Candy Cane - Red and white stripes
# Classic Christmas candy cane pattern
# Classic Christmas candy cane animation
strip length 60
#strip length 60
# Define stripe colors
color candy_red = 0xFF0000
color candy_white = 0xFFFFFF
# Create alternating red and white pattern
# Create alternating red and white animation
# Using multiple pulse positions to create stripes
animation stripe1 = pulse_position_animation(candy_red, 3, 4, 1)
animation stripe2 = pulse_position_animation(candy_white, 9, 4, 1)
animation stripe3 = pulse_position_animation(candy_red, 15, 4, 1)
animation stripe4 = pulse_position_animation(candy_white, 21, 4, 1)
animation stripe5 = pulse_position_animation(candy_red, 27, 4, 1)
animation stripe6 = pulse_position_animation(candy_white, 33, 4, 1)
animation stripe7 = pulse_position_animation(candy_red, 39, 4, 1)
animation stripe8 = pulse_position_animation(candy_white, 45, 4, 1)
animation stripe9 = pulse_position_animation(candy_red, 51, 4, 1)
animation stripe10 = pulse_position_animation(candy_white, 57, 4, 1)
animation stripe1 = beacon_animation(color=candy_red, pos=3, beacon_size=4, slew_size=1)
animation stripe2 = beacon_animation(color=candy_white, pos=9, beacon_size=4, slew_size=1)
animation stripe3 = beacon_animation(color=candy_red, pos=15, beacon_size=4, slew_size=1)
animation stripe4 = beacon_animation(color=candy_white, pos=21, beacon_size=4, slew_size=1)
animation stripe5 = beacon_animation(color=candy_red, pos=27, beacon_size=4, slew_size=1)
animation stripe6 = beacon_animation(color=candy_white, pos=33, beacon_size=4, slew_size=1)
animation stripe7 = beacon_animation(color=candy_red, pos=39, beacon_size=4, slew_size=1)
animation stripe8 = beacon_animation(color=candy_white, pos=45, beacon_size=4, slew_size=1)
animation stripe9 = beacon_animation(color=candy_red, pos=51, beacon_size=4, slew_size=1)
animation stripe10 = beacon_animation(color=candy_white, pos=57, beacon_size=4, slew_size=1)
# Add gentle movement to make it more dynamic
set move_speed = 8s
stripe1.pos = sawtooth(3, 63, move_speed)
stripe2.pos = sawtooth(9, 69, move_speed)
stripe3.pos = sawtooth(15, 75, move_speed)
stripe4.pos = sawtooth(21, 81, move_speed)
stripe5.pos = sawtooth(27, 87, move_speed)
stripe6.pos = sawtooth(33, 93, move_speed)
stripe7.pos = sawtooth(39, 99, move_speed)
stripe8.pos = sawtooth(45, 105, move_speed)
stripe9.pos = sawtooth(51, 111, move_speed)
stripe10.pos = sawtooth(57, 117, move_speed)
stripe1.pos = sawtooth(min_value=3, max_value=63, duration=move_speed)
stripe2.pos = sawtooth(min_value=9, max_value=69, duration=move_speed)
stripe3.pos = sawtooth(min_value=15, max_value=75, duration=move_speed)
stripe4.pos = sawtooth(min_value=21, max_value=81, duration=move_speed)
stripe5.pos = sawtooth(min_value=27, max_value=87, duration=move_speed)
stripe6.pos = sawtooth(min_value=33, max_value=93, duration=move_speed)
stripe7.pos = sawtooth(min_value=39, max_value=99, duration=move_speed)
stripe8.pos = sawtooth(min_value=45, max_value=105, duration=move_speed)
stripe9.pos = sawtooth(min_value=51, max_value=111, duration=move_speed)
stripe10.pos = sawtooth(min_value=57, max_value=117, duration=move_speed)
# Start all stripes
run stripe1

View File

@ -1,11 +1,11 @@
# Christmas Tree - Festive holiday colors
# Green base with colorful ornaments and twinkling
strip length 60
#strip length 60
# Green tree base
color tree_green = 0x006600
animation tree_base = solid(tree_green)
animation tree_base = solid(color=tree_green)
# Define ornament colors
palette ornament_colors = [
@ -17,38 +17,38 @@ palette ornament_colors = [
]
# Colorful ornaments as twinkling lights
pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
color ornament_pattern = rich_palette(palette=ornament_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
animation ornaments = twinkle_animation(
ornament_pattern, # color source
15, # density (many ornaments)
800ms # twinkle speed (slow twinkle)
color=ornament_pattern, # color source
density=15, # density (many ornaments)
twinkle_speed=800ms # twinkle speed (slow twinkle)
)
ornaments.priority = 10
# Star on top (bright yellow pulse)
animation tree_star = pulse_position_animation(
0xFFFF00, # Bright yellow
58, # position (near the top)
3, # star size
1 # sharp edges
animation tree_star = beacon_animation(
color=0xFFFF00, # Bright yellow
pos=58, # position (near the top)
beacon_size=3, # star size
slew_size=1 # sharp edges
)
tree_star.priority = 20
tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
tree_star.opacity = smooth(min_value=200, max_value=255, duration=2s) # Gentle pulsing
# Add some white sparkles for snow/magic
animation snow_sparkles = twinkle_animation(
0xFFFFFF, # White snow
8, # density (sparkle count)
400ms # twinkle speed (quick sparkles)
color=0xFFFFFF, # White snow
density=8, # density (sparkle count)
twinkle_speed=400ms # twinkle speed (quick sparkles)
)
snow_sparkles.priority = 15
# Garland effect - moving colored lights
pattern garland_pattern = rich_palette_color_provider(ornament_colors, 2s, linear, 200)
color garland_pattern = rich_palette(palette=ornament_colors, cycle_period=2s, transition_type=LINEAR, brightness=200)
animation garland = comet_animation(
garland_pattern, # color source
6, # garland length (tail length)
4s # slow movement (speed)
color=garland_pattern, # color source
tail_length=6, # garland length (tail length)
speed=4s # slow movement (speed)
)
garland.priority = 5

View File

@ -1,34 +1,34 @@
# Comet Chase - Moving comet with trailing tail
# Bright head with fading tail
strip length 60
#strip length 60
# Dark blue background
color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
animation background = solid(space_blue)
animation background = solid(color=space_blue)
# Main comet with bright white head
animation comet_main = comet_animation(
0xFFFFFF, # White head
10, # tail length
2s # speed
color=0xFFFFFF, # White head
tail_length=10, # tail length
speed=2s # speed
)
comet_main.priority = 7
# Secondary comet in different color, opposite direction
animation comet_secondary = comet_animation(
0xFF4500, # Orange head
8, # shorter tail
3s, # slower speed
-1 # other direction
color=0xFF4500, # Orange head
tail_length=8, # shorter tail
speed=3s, # slower speed
direction=-1 # other direction
)
comet_secondary.priority = 5
# Add sparkle trail behind comets but on top of blue background
animation comet_sparkles = twinkle_animation(
0xAAAAFF, # Light blue sparkles
8, # density (moderate sparkles)
400ms # twinkle speed (quick sparkle)
color=0xAAAAFF, # Light blue sparkles
density=8, # density (moderate sparkles)
twinkle_speed=400ms # twinkle speed (quick sparkle)
)
comet_sparkles.priority = 8

View File

@ -1,126 +0,0 @@
# Compiled DSL Examples
This directory contains the results of compiling Animation DSL examples to Berry code.
## Files
- `COMPILATION_REPORT.md` - Detailed compilation results and analysis
- `run_successful_tests.sh` - Test runner for successfully compiled examples
- `*.be` - Compiled Berry code files from DSL sources
## Current Status
The DSL transpiler has been significantly improved and now successfully compiles all example DSL files!
### What Works ✅
- **Basic color definitions** (`color red = #FF0000`)
- **Palette definitions with comments** (`palette colors = [(0, #000000), # Black]`)
- **Pattern definitions** (`pattern solid_red = solid(red)`)
- **Animation definitions** (`animation anim1 = pulse_position(...)`)
- **Function calls with inline comments** (multiline functions with comments)
- **Easing keywords** (`smooth`, `linear`, `ease_in`, `ease_out`, `bounce`, `elastic`)
- **Strip configuration** (`strip length 60`)
- **Variable assignments** (`set var = value`)
- **Run statements** (`run animation_name`)
- **Complex nested function calls**
- **All 23 example DSL files compile successfully**
### Recent Improvements ✅
1. **Fixed Comments in Palette Definitions**: Palette arrays can now include inline comments
```dsl
palette fire_colors = [
(0, #000000), # Black (no fire) - This now works!
(128, #FF0000), # Red flames
(255, #FFFF00) # Bright yellow
]
```
2. **Fixed Comments in Function Arguments**: Multiline function calls with comments now parse correctly
```dsl
animation lava_blob = pulse_position(
rich_palette(lava_colors, 12s, smooth, 255),
18, # large blob - This now works!
12, # very soft edges
10, # priority
loop
)
```
3. **Added Easing Keyword Support**: Keywords like `smooth`, `linear` are now recognized
```dsl
animation smooth_fade = filled(
rich_palette(colors, 5s, smooth, 255), # 'smooth' now works!
loop
)
```
### What Still Needs Work ❌
- **Property assignments** (`animation.pos = value`) - Not yet supported
- **Multiple run statements** (generates multiple engine.start() calls)
- **Advanced DSL features** (sequences, loops, conditionals)
- **Runtime execution** (compiled code may have runtime issues)
### Example Working DSL
```dsl
# Complex working example with comments and palettes
strip length 60
# Define colors with comments
palette lava_colors = [
(0, #330000), # Dark red
(64, #660000), # Medium red
(128, #CC3300), # Bright red
(192, #FF6600), # Orange
(255, #FFAA00) # Yellow-orange
]
# Animation with inline comments
animation lava_base = filled(
rich_palette(lava_colors, 15s, smooth, 180), # Smooth transitions
loop
)
animation lava_blob = pulse_position(
rich_palette(lava_colors, 12s, smooth, 255),
18, # large blob
12, # very soft edges
10, # priority
loop
)
run lava_base
run lava_blob
```
## Usage
To compile DSL examples:
```bash
./compile_all_dsl_examples.sh
```
To test compiled examples:
```bash
./anim_examples/compiled/run_successful_tests.sh
```
## Success Rate
- **Current**: 100% (23/23 files compile successfully)
- **Previous**: 4% (1/23 files)
- **Improvement**: 575% increase in successful compilations
## Development Notes
The DSL transpiler uses a single-pass architecture that directly converts tokens to Berry code. Recent improvements:
1. ✅ **Enhanced comment handling** - Comments now work in all contexts
2. ✅ **Easing keyword support** - All easing functions recognized
3. ✅ **Improved error handling** - Better parsing of complex expressions
4. ❌ **Property assignments** - Still need implementation
5. ❌ **Advanced DSL features** - Sequences, loops, conditionals not yet supported

View File

@ -2,7 +2,7 @@
# Source: aurora_borealis.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
@ -31,10 +31,10 @@
#
# # Base aurora animation with slow flowing colors
# animation aurora_base = rich_palette_animation(
# aurora_colors, # palette
# 10s, # cycle period
# smooth, # transition type (explicit for clarity)
# 180 # brightness (dimmed for aurora effect)
# palette=aurora_colors, # palette
# cycle_period=10s, # cycle period
# transition_type=SINE, # transition type (explicit for clarity)
# brightness=180 # brightness (dimmed for aurora effect)
# )
#
# sequence demo {
@ -56,7 +56,11 @@ var aurora_colors_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FFAA
# Secondary purple palette
var aurora_purple_ = bytes("00220022" "40440044" "808800AA" "C0AA44CC" "FFCCAAFF")
# Base aurora animation with slow flowing colors
var aurora_base_ = animation.rich_palette_animation(animation.global('aurora_colors_', 'aurora_colors'), 10000, animation.global('smooth_', 'smooth'), 180)
var aurora_base_ = animation.rich_palette_animation(engine)
aurora_base_.palette = animation.global('aurora_colors_', 'aurora_colors')
aurora_base_.cycle_period = 10000
aurora_base_.transition_type = animation.global('SINE_', 'SINE')
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
def sequence_demo()
var steps = []
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)

View File

@ -2,14 +2,14 @@
# Source: breathing_colors.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Breathing Colors - Slow color breathing effect
# # Gentle pulsing through different colors
#
# strip length 60
# #strip length 60
#
# # Define breathing colors
# color breathe_red = 0xFF0000
@ -29,21 +29,21 @@
# ]
#
# # Create a rich palette color provider
# pattern palette_pattern = rich_palette_animation(
# breathe_palette, # palette
# 15s # cycle period (defaults: smooth transition, 255 brightness)
# color palette_pattern = rich_palette(
# palette=breathe_palette, # palette
# cycle_period=15s # cycle period (defaults: smooth transition, 255 brightness)
# )
#
# # Create breathing animation using the palette
# animation breathing = breathe(
# palette_pattern, # base pattern
# 100, # min brightness
# 255, # max brightness
# 4s # breathing period (4 seconds)
# animation breathing = breathe_animation(
# color=palette_pattern, # base animation
# min_brightness=100, # min brightness
# max_brightness=255, # max brightness
# period=4s # breathing period (4 seconds)
# )
#
# # Add gentle opacity breathing
# breathing.opacity = smooth(100, 255, 4s)
# breathing.opacity = smooth(min_value=100, max_value=255, duration=4s)
#
# # Start the animation
# run breathing
@ -52,8 +52,11 @@ import animation
# Breathing Colors - Slow color breathing effect
# Gentle pulsing through different colors
var engine = animation.init_strip(60)
#strip length 60
# Define breathing colors
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var breathe_red_ = 0xFFFF0000
var breathe_green_ = 0xFF00FF00
var breathe_blue_ = 0xFF0000FF
@ -62,11 +65,21 @@ var breathe_orange_ = 0xFFFF8000
# Create breathing animation that cycles through colors
var breathe_palette_ = bytes("00FF0000" "33FF8000" "66FFFF00" "9900FF00" "CC0000FF" "FF800080")
# Create a rich palette color provider
var palette_pattern_ = animation.rich_palette_animation(animation.global('breathe_palette_', 'breathe_palette'), 15000)
var palette_pattern_ = animation.rich_palette(engine)
palette_pattern_.palette = animation.global('breathe_palette_', 'breathe_palette')
palette_pattern_.cycle_period = 15000 # cycle period (defaults: smooth transition, 255 brightness)
# Create breathing animation using the palette
var breathing_ = animation.breathe(animation.global('palette_pattern_', 'palette_pattern'), 100, 255, 4000)
var breathing_ = animation.breathe_animation(engine)
breathing_.color = animation.global('palette_pattern_', 'palette_pattern')
breathing_.min_brightness = 100
breathing_.max_brightness = 255
breathing_.period = 4000 # breathing period (4 seconds)
# Add gentle opacity breathing
animation.global('breathing_').opacity = animation.smooth(100, 255, 4000)
var temp_smooth_156 = animation.smooth(engine)
temp_smooth_156.min_value = 100
temp_smooth_156.max_value = 255
temp_smooth_156.duration = 4000
animation.global('breathing_').opacity = temp_smooth_156
# Start the animation
# Start all animations/sequences
if global.contains('sequence_breathing')

View File

@ -2,44 +2,44 @@
# Source: candy_cane.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Candy Cane - Red and white stripes
# # Classic Christmas candy cane pattern
# # Classic Christmas candy cane animation
#
# strip length 60
# #strip length 60
#
# # Define stripe colors
# color candy_red = 0xFF0000
# color candy_white = 0xFFFFFF
#
# # Create alternating red and white pattern
# # Create alternating red and white animation
# # Using multiple pulse positions to create stripes
# animation stripe1 = pulse_position_animation(candy_red, 3, 4, 1)
# animation stripe2 = pulse_position_animation(candy_white, 9, 4, 1)
# animation stripe3 = pulse_position_animation(candy_red, 15, 4, 1)
# animation stripe4 = pulse_position_animation(candy_white, 21, 4, 1)
# animation stripe5 = pulse_position_animation(candy_red, 27, 4, 1)
# animation stripe6 = pulse_position_animation(candy_white, 33, 4, 1)
# animation stripe7 = pulse_position_animation(candy_red, 39, 4, 1)
# animation stripe8 = pulse_position_animation(candy_white, 45, 4, 1)
# animation stripe9 = pulse_position_animation(candy_red, 51, 4, 1)
# animation stripe10 = pulse_position_animation(candy_white, 57, 4, 1)
# animation stripe1 = beacon_animation(color=candy_red, pos=3, beacon_size=4, slew_size=1)
# animation stripe2 = beacon_animation(color=candy_white, pos=9, beacon_size=4, slew_size=1)
# animation stripe3 = beacon_animation(color=candy_red, pos=15, beacon_size=4, slew_size=1)
# animation stripe4 = beacon_animation(color=candy_white, pos=21, beacon_size=4, slew_size=1)
# animation stripe5 = beacon_animation(color=candy_red, pos=27, beacon_size=4, slew_size=1)
# animation stripe6 = beacon_animation(color=candy_white, pos=33, beacon_size=4, slew_size=1)
# animation stripe7 = beacon_animation(color=candy_red, pos=39, beacon_size=4, slew_size=1)
# animation stripe8 = beacon_animation(color=candy_white, pos=45, beacon_size=4, slew_size=1)
# animation stripe9 = beacon_animation(color=candy_red, pos=51, beacon_size=4, slew_size=1)
# animation stripe10 = beacon_animation(color=candy_white, pos=57, beacon_size=4, slew_size=1)
#
# # Add gentle movement to make it more dynamic
# set move_speed = 8s
# stripe1.pos = sawtooth(3, 63, move_speed)
# stripe2.pos = sawtooth(9, 69, move_speed)
# stripe3.pos = sawtooth(15, 75, move_speed)
# stripe4.pos = sawtooth(21, 81, move_speed)
# stripe5.pos = sawtooth(27, 87, move_speed)
# stripe6.pos = sawtooth(33, 93, move_speed)
# stripe7.pos = sawtooth(39, 99, move_speed)
# stripe8.pos = sawtooth(45, 105, move_speed)
# stripe9.pos = sawtooth(51, 111, move_speed)
# stripe10.pos = sawtooth(57, 117, move_speed)
# stripe1.pos = sawtooth(min_value=3, max_value=63, duration=move_speed)
# stripe2.pos = sawtooth(min_value=9, max_value=69, duration=move_speed)
# stripe3.pos = sawtooth(min_value=15, max_value=75, duration=move_speed)
# stripe4.pos = sawtooth(min_value=21, max_value=81, duration=move_speed)
# stripe5.pos = sawtooth(min_value=27, max_value=87, duration=move_speed)
# stripe6.pos = sawtooth(min_value=33, max_value=93, duration=move_speed)
# stripe7.pos = sawtooth(min_value=39, max_value=99, duration=move_speed)
# stripe8.pos = sawtooth(min_value=45, max_value=105, duration=move_speed)
# stripe9.pos = sawtooth(min_value=51, max_value=111, duration=move_speed)
# stripe10.pos = sawtooth(min_value=57, max_value=117, duration=move_speed)
#
# # Start all stripes
# run stripe1
@ -56,35 +56,118 @@
import animation
# Candy Cane - Red and white stripes
# Classic Christmas candy cane pattern
var engine = animation.init_strip(60)
# Classic Christmas candy cane animation
#strip length 60
# Define stripe colors
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var candy_red_ = 0xFFFF0000
var candy_white_ = 0xFFFFFFFF
# Create alternating red and white pattern
# Create alternating red and white animation
# Using multiple pulse positions to create stripes
var stripe1_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 3, 4, 1)
var stripe2_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 9, 4, 1)
var stripe3_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 15, 4, 1)
var stripe4_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 21, 4, 1)
var stripe5_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 27, 4, 1)
var stripe6_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 33, 4, 1)
var stripe7_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 39, 4, 1)
var stripe8_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 45, 4, 1)
var stripe9_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 51, 4, 1)
var stripe10_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 57, 4, 1)
var stripe1_ = animation.beacon_animation(engine)
stripe1_.color = animation.global('candy_red_', 'candy_red')
stripe1_.pos = 3
stripe1_.beacon_size = 4
stripe1_.slew_size = 1
var stripe2_ = animation.beacon_animation(engine)
stripe2_.color = animation.global('candy_white_', 'candy_white')
stripe2_.pos = 9
stripe2_.beacon_size = 4
stripe2_.slew_size = 1
var stripe3_ = animation.beacon_animation(engine)
stripe3_.color = animation.global('candy_red_', 'candy_red')
stripe3_.pos = 15
stripe3_.beacon_size = 4
stripe3_.slew_size = 1
var stripe4_ = animation.beacon_animation(engine)
stripe4_.color = animation.global('candy_white_', 'candy_white')
stripe4_.pos = 21
stripe4_.beacon_size = 4
stripe4_.slew_size = 1
var stripe5_ = animation.beacon_animation(engine)
stripe5_.color = animation.global('candy_red_', 'candy_red')
stripe5_.pos = 27
stripe5_.beacon_size = 4
stripe5_.slew_size = 1
var stripe6_ = animation.beacon_animation(engine)
stripe6_.color = animation.global('candy_white_', 'candy_white')
stripe6_.pos = 33
stripe6_.beacon_size = 4
stripe6_.slew_size = 1
var stripe7_ = animation.beacon_animation(engine)
stripe7_.color = animation.global('candy_red_', 'candy_red')
stripe7_.pos = 39
stripe7_.beacon_size = 4
stripe7_.slew_size = 1
var stripe8_ = animation.beacon_animation(engine)
stripe8_.color = animation.global('candy_white_', 'candy_white')
stripe8_.pos = 45
stripe8_.beacon_size = 4
stripe8_.slew_size = 1
var stripe9_ = animation.beacon_animation(engine)
stripe9_.color = animation.global('candy_red_', 'candy_red')
stripe9_.pos = 51
stripe9_.beacon_size = 4
stripe9_.slew_size = 1
var stripe10_ = animation.beacon_animation(engine)
stripe10_.color = animation.global('candy_white_', 'candy_white')
stripe10_.pos = 57
stripe10_.beacon_size = 4
stripe10_.slew_size = 1
# Add gentle movement to make it more dynamic
var move_speed_ = 8000
animation.global('stripe1_').pos = animation.sawtooth(3, 63, animation.global('move_speed_', 'move_speed'))
animation.global('stripe2_').pos = animation.sawtooth(9, 69, animation.global('move_speed_', 'move_speed'))
animation.global('stripe3_').pos = animation.sawtooth(15, 75, animation.global('move_speed_', 'move_speed'))
animation.global('stripe4_').pos = animation.sawtooth(21, 81, animation.global('move_speed_', 'move_speed'))
animation.global('stripe5_').pos = animation.sawtooth(27, 87, animation.global('move_speed_', 'move_speed'))
animation.global('stripe6_').pos = animation.sawtooth(33, 93, animation.global('move_speed_', 'move_speed'))
animation.global('stripe7_').pos = animation.sawtooth(39, 99, animation.global('move_speed_', 'move_speed'))
animation.global('stripe8_').pos = animation.sawtooth(45, 105, animation.global('move_speed_', 'move_speed'))
animation.global('stripe9_').pos = animation.sawtooth(51, 111, animation.global('move_speed_', 'move_speed'))
animation.global('stripe10_').pos = animation.sawtooth(57, 117, animation.global('move_speed_', 'move_speed'))
var temp_sawtooth_258 = animation.sawtooth(engine)
temp_sawtooth_258.min_value = 3
temp_sawtooth_258.max_value = 63
temp_sawtooth_258.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe1_').pos = temp_sawtooth_258
var temp_sawtooth_277 = animation.sawtooth(engine)
temp_sawtooth_277.min_value = 9
temp_sawtooth_277.max_value = 69
temp_sawtooth_277.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe2_').pos = temp_sawtooth_277
var temp_sawtooth_296 = animation.sawtooth(engine)
temp_sawtooth_296.min_value = 15
temp_sawtooth_296.max_value = 75
temp_sawtooth_296.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe3_').pos = temp_sawtooth_296
var temp_sawtooth_315 = animation.sawtooth(engine)
temp_sawtooth_315.min_value = 21
temp_sawtooth_315.max_value = 81
temp_sawtooth_315.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe4_').pos = temp_sawtooth_315
var temp_sawtooth_334 = animation.sawtooth(engine)
temp_sawtooth_334.min_value = 27
temp_sawtooth_334.max_value = 87
temp_sawtooth_334.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe5_').pos = temp_sawtooth_334
var temp_sawtooth_353 = animation.sawtooth(engine)
temp_sawtooth_353.min_value = 33
temp_sawtooth_353.max_value = 93
temp_sawtooth_353.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe6_').pos = temp_sawtooth_353
var temp_sawtooth_372 = animation.sawtooth(engine)
temp_sawtooth_372.min_value = 39
temp_sawtooth_372.max_value = 99
temp_sawtooth_372.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe7_').pos = temp_sawtooth_372
var temp_sawtooth_391 = animation.sawtooth(engine)
temp_sawtooth_391.min_value = 45
temp_sawtooth_391.max_value = 105
temp_sawtooth_391.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe8_').pos = temp_sawtooth_391
var temp_sawtooth_410 = animation.sawtooth(engine)
temp_sawtooth_410.min_value = 51
temp_sawtooth_410.max_value = 111
temp_sawtooth_410.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe9_').pos = temp_sawtooth_410
var temp_sawtooth_429 = animation.sawtooth(engine)
temp_sawtooth_429.min_value = 57
temp_sawtooth_429.max_value = 117
temp_sawtooth_429.duration = animation.global('move_speed_', 'move_speed')
animation.global('stripe10_').pos = temp_sawtooth_429
# Start all stripes
# Start all animations/sequences
if global.contains('sequence_stripe1')

View File

@ -2,18 +2,18 @@
# Source: christmas_tree.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Christmas Tree - Festive holiday colors
# # Green base with colorful ornaments and twinkling
#
# strip length 60
# #strip length 60
#
# # Green tree base
# color tree_green = 0x006600
# animation tree_base = solid(tree_green)
# animation tree_base = solid(color=tree_green)
#
# # Define ornament colors
# palette ornament_colors = [
@ -25,38 +25,38 @@
# ]
#
# # Colorful ornaments as twinkling lights
# pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
# color ornament_pattern = rich_palette(palette=ornament_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
# animation ornaments = twinkle_animation(
# ornament_pattern, # color source
# 15, # density (many ornaments)
# 800ms # twinkle speed (slow twinkle)
# color=ornament_pattern, # color source
# density=15, # density (many ornaments)
# twinkle_speed=800ms # twinkle speed (slow twinkle)
# )
# ornaments.priority = 10
#
# # Star on top (bright yellow pulse)
# animation tree_star = pulse_position_animation(
# 0xFFFF00, # Bright yellow
# 58, # position (near the top)
# 3, # star size
# 1 # sharp edges
# animation tree_star = beacon_animation(
# color=0xFFFF00, # Bright yellow
# pos=58, # position (near the top)
# beacon_size=3, # star size
# slew_size=1 # sharp edges
# )
# tree_star.priority = 20
# tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
# tree_star.opacity = smooth(min_value=200, max_value=255, duration=2s) # Gentle pulsing
#
# # Add some white sparkles for snow/magic
# animation snow_sparkles = twinkle_animation(
# 0xFFFFFF, # White snow
# 8, # density (sparkle count)
# 400ms # twinkle speed (quick sparkles)
# color=0xFFFFFF, # White snow
# density=8, # density (sparkle count)
# twinkle_speed=400ms # twinkle speed (quick sparkles)
# )
# snow_sparkles.priority = 15
#
# # Garland effect - moving colored lights
# pattern garland_pattern = rich_palette_color_provider(ornament_colors, 2s, linear, 200)
# color garland_pattern = rich_palette(palette=ornament_colors, cycle_period=2s, transition_type=LINEAR, brightness=200)
# animation garland = comet_animation(
# garland_pattern, # color source
# 6, # garland length (tail length)
# 4s # slow movement (speed)
# color=garland_pattern, # color source
# tail_length=6, # garland length (tail length)
# speed=4s # slow movement (speed)
# )
# garland.priority = 5
#
@ -71,26 +71,55 @@ import animation
# Christmas Tree - Festive holiday colors
# Green base with colorful ornaments and twinkling
var engine = animation.init_strip(60)
#strip length 60
# Green tree base
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var tree_green_ = 0xFF006600
var tree_base_ = animation.solid(animation.global('tree_green_', 'tree_green'))
var tree_base_ = animation.solid(engine)
tree_base_.color = animation.global('tree_green_', 'tree_green')
# Define ornament colors
var ornament_colors_ = bytes("00FF0000" "40FFD700" "800000FF" "C0FFFFFF" "FFFF00FF")
# Colorful ornaments as twinkling lights
var ornament_pattern_ = animation.rich_palette_color_provider(animation.global('ornament_colors_', 'ornament_colors'), 3000, animation.global('linear_', 'linear'), 255)
var ornaments_ = animation.twinkle_animation(animation.global('ornament_pattern_', 'ornament_pattern'), 15, 800)
var ornament_pattern_ = animation.rich_palette(engine)
ornament_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
ornament_pattern_.cycle_period = 3000
ornament_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
ornament_pattern_.brightness = 255
var ornaments_ = animation.twinkle_animation(engine)
ornaments_.color = animation.global('ornament_pattern_', 'ornament_pattern')
ornaments_.density = 15
ornaments_.twinkle_speed = 800 # twinkle speed (slow twinkle)
animation.global('ornaments_').priority = 10
# Star on top (bright yellow pulse)
var tree_star_ = animation.pulse_position_animation(0xFFFFFF00, 58, 3, 1)
var tree_star_ = animation.beacon_animation(engine)
tree_star_.color = 0xFFFFFF00
tree_star_.pos = 58
tree_star_.beacon_size = 3
tree_star_.slew_size = 1 # sharp edges
animation.global('tree_star_').priority = 20
animation.global('tree_star_').opacity = animation.smooth(200, 255, 2000) # Gentle pulsing
var temp_smooth_175 = animation.smooth(engine)
temp_smooth_175.min_value = 200
temp_smooth_175.max_value = 255
temp_smooth_175.duration = 2000
animation.global('tree_star_').opacity = temp_smooth_175 # Gentle pulsing
# Add some white sparkles for snow/magic
var snow_sparkles_ = animation.twinkle_animation(0xFFFFFFFF, 8, 400)
var snow_sparkles_ = animation.twinkle_animation(engine)
snow_sparkles_.color = 0xFFFFFFFF
snow_sparkles_.density = 8
snow_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkles)
animation.global('snow_sparkles_').priority = 15
# Garland effect - moving colored lights
var garland_pattern_ = animation.rich_palette_color_provider(animation.global('ornament_colors_', 'ornament_colors'), 2000, animation.global('linear_', 'linear'), 200)
var garland_ = animation.comet_animation(animation.global('garland_pattern_', 'garland_pattern'), 6, 4000)
var garland_pattern_ = animation.rich_palette(engine)
garland_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
garland_pattern_.cycle_period = 2000
garland_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
garland_pattern_.brightness = 200
var garland_ = animation.comet_animation(engine)
garland_.color = animation.global('garland_pattern_', 'garland_pattern')
garland_.tail_length = 6
garland_.speed = 4000 # slow movement (speed)
animation.global('garland_').priority = 5
# Start all animations
# Start all animations/sequences

View File

@ -2,41 +2,41 @@
# Source: comet_chase.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Comet Chase - Moving comet with trailing tail
# # Bright head with fading tail
#
# strip length 60
# #strip length 60
#
# # Dark blue background
# color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
# animation background = solid(space_blue)
# animation background = solid(color=space_blue)
#
# # Main comet with bright white head
# animation comet_main = comet_animation(
# 0xFFFFFF, # White head
# 10, # tail length
# 2s # speed
# color=0xFFFFFF, # White head
# tail_length=10, # tail length
# speed=2s # speed
# )
# comet_main.priority = 7
#
# # Secondary comet in different color, opposite direction
# animation comet_secondary = comet_animation(
# 0xFF4500, # Orange head
# 8, # shorter tail
# 3s, # slower speed
# -1 # other direction
# color=0xFF4500, # Orange head
# tail_length=8, # shorter tail
# speed=3s, # slower speed
# direction=-1 # other direction
# )
# comet_secondary.priority = 5
#
# # Add sparkle trail behind comets but on top of blue background
# animation comet_sparkles = twinkle_animation(
# 0xAAAAFF, # Light blue sparkles
# 8, # density (moderate sparkles)
# 400ms # twinkle speed (quick sparkle)
# color=0xAAAAFF, # Light blue sparkles
# density=8, # density (moderate sparkles)
# twinkle_speed=400ms # twinkle speed (quick sparkle)
# )
# comet_sparkles.priority = 8
#
@ -50,18 +50,32 @@ import animation
# Comet Chase - Moving comet with trailing tail
# Bright head with fading tail
var engine = animation.init_strip(60)
#strip length 60
# Dark blue background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var space_blue_ = 0xFF000066 # Note: opaque 0xFF alpha channel is implicitly added
var background_ = animation.solid(animation.global('space_blue_', 'space_blue'))
var background_ = animation.solid(engine)
background_.color = animation.global('space_blue_', 'space_blue')
# Main comet with bright white head
var comet_main_ = animation.comet_animation(0xFFFFFFFF, 10, 2000)
var comet_main_ = animation.comet_animation(engine)
comet_main_.color = 0xFFFFFFFF
comet_main_.tail_length = 10
comet_main_.speed = 2000 # speed
animation.global('comet_main_').priority = 7
# Secondary comet in different color, opposite direction
var comet_secondary_ = animation.comet_animation(0xFFFF4500, 8, 3000, -1)
var comet_secondary_ = animation.comet_animation(engine)
comet_secondary_.color = 0xFFFF4500
comet_secondary_.tail_length = 8
comet_secondary_.speed = 3000
comet_secondary_.direction = -1 # other direction
animation.global('comet_secondary_').priority = 5
# Add sparkle trail behind comets but on top of blue background
var comet_sparkles_ = animation.twinkle_animation(0xFFAAAAFF, 8, 400)
var comet_sparkles_ = animation.twinkle_animation(engine)
comet_sparkles_.color = 0xFFAAAAFF
comet_sparkles_.density = 8
comet_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkle)
animation.global('comet_sparkles_').priority = 8
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: disco_strobe.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Disco Strobe - Fast colorful strobing
# # Rapid color changes with strobe effects
#
# strip length 60
# #strip length 60
#
# # Define disco color palette
# palette disco_colors = [
@ -23,35 +23,35 @@
# ]
#
# # Fast color cycling base
# animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
# animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
#
# # Add strobe effect
# disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
# disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
#
# # Add white flash overlay
# animation white_flash = solid(0xFFFFFF)
# white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
# animation white_flash = solid(color=0xFFFFFF)
# white_flash.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=10) # Quick white flashes
# white_flash.priority = 20
#
# # Add colored sparkles
# pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
# color sparkle_pattern = rich_palette(palette=disco_colors, cycle_period=500ms, transition_type=LINEAR, brightness=255)
# animation disco_sparkles = twinkle_animation(
# sparkle_pattern, # color source
# 12, # density (many sparkles)
# 80ms # twinkle speed (very quick)
# color=sparkle_pattern, # color source
# density=12, # density (many sparkles)
# twinkle_speed=80ms # twinkle speed (very quick)
# )
# disco_sparkles.priority = 15
#
# # Add moving pulse for extra effect
# pattern pulse_pattern = rich_palette_color_provider(disco_colors, 800ms, linear, 255)
# animation disco_pulse = pulse_position_animation(
# pulse_pattern, # color source
# 4, # initial position
# 8, # pulse width
# 2 # sharp edges (slew size)
# color pulse_pattern = rich_palette(palette=disco_colors, cycle_period=800ms, transition_type=LINEAR, brightness=255)
# animation disco_pulse = beacon_animation(
# color=pulse_pattern, # color source
# pos=4, # initial position
# beacon_size=8, # pulse width
# slew_size=2 # sharp edges (slew size)
# )
# disco_pulse.priority = 10
# disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
# disco_pulse.pos = sawtooth(min_value=4, max_value=56, duration=2s) # Fast movement
#
# # Start all animations
# run disco_base
@ -63,26 +63,63 @@ import animation
# Disco Strobe - Fast colorful strobing
# Rapid color changes with strobe effects
var engine = animation.init_strip(60)
#strip length 60
# Define disco color palette
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var disco_colors_ = bytes("00FF0000" "2AFF8000" "55FFFF00" "8000FF00" "AA0000FF" "D58000FF" "FFFF00FF")
# Fast color cycling base
var disco_base_ = animation.rich_palette_animation(animation.global('disco_colors_', 'disco_colors'), 1000, animation.global('linear_', 'linear'), 255)
var disco_base_ = animation.rich_palette_animation(engine)
disco_base_.palette = animation.global('disco_colors_', 'disco_colors')
disco_base_.cycle_period = 1000
disco_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
disco_base_.brightness = 255
# Add strobe effect
animation.global('disco_base_').opacity = animation.square(0, 255, 100, 30) # Fast strobe
var temp_square_105 = animation.square(engine)
temp_square_105.min_value = 0
temp_square_105.max_value = 255
temp_square_105.duration = 100
temp_square_105.duty_cycle = 30
animation.global('disco_base_').opacity = temp_square_105 # Fast strobe
# Add white flash overlay
var white_flash_ = animation.solid(0xFFFFFFFF)
animation.global('white_flash_').opacity = animation.square(0, 255, 50, 10) # Quick white flashes
var white_flash_ = animation.solid(engine)
white_flash_.color = 0xFFFFFFFF
var temp_square_142 = animation.square(engine)
temp_square_142.min_value = 0
temp_square_142.max_value = 255
temp_square_142.duration = 50
temp_square_142.duty_cycle = 10
animation.global('white_flash_').opacity = temp_square_142 # Quick white flashes
animation.global('white_flash_').priority = 20
# Add colored sparkles
var sparkle_pattern_ = animation.rich_palette_color_provider(animation.global('disco_colors_', 'disco_colors'), 500, animation.global('linear_', 'linear'), 255)
var disco_sparkles_ = animation.twinkle_animation(animation.global('sparkle_pattern_', 'sparkle_pattern'), 12, 80)
var sparkle_pattern_ = animation.rich_palette(engine)
sparkle_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
sparkle_pattern_.cycle_period = 500
sparkle_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
sparkle_pattern_.brightness = 255
var disco_sparkles_ = animation.twinkle_animation(engine)
disco_sparkles_.color = animation.global('sparkle_pattern_', 'sparkle_pattern')
disco_sparkles_.density = 12
disco_sparkles_.twinkle_speed = 80 # twinkle speed (very quick)
animation.global('disco_sparkles_').priority = 15
# Add moving pulse for extra effect
var pulse_pattern_ = animation.rich_palette_color_provider(animation.global('disco_colors_', 'disco_colors'), 800, animation.global('linear_', 'linear'), 255)
var disco_pulse_ = animation.pulse_position_animation(animation.global('pulse_pattern_', 'pulse_pattern'), 4, 8, 2)
var pulse_pattern_ = animation.rich_palette(engine)
pulse_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
pulse_pattern_.cycle_period = 800
pulse_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
pulse_pattern_.brightness = 255
var disco_pulse_ = animation.beacon_animation(engine)
disco_pulse_.color = animation.global('pulse_pattern_', 'pulse_pattern')
disco_pulse_.pos = 4
disco_pulse_.beacon_size = 8
disco_pulse_.slew_size = 2 # sharp edges (slew size)
animation.global('disco_pulse_').priority = 10
animation.global('disco_pulse_').pos = animation.sawtooth(4, 56, 2000) # Fast movement
var temp_sawtooth_290 = animation.sawtooth(engine)
temp_sawtooth_290.min_value = 4
temp_sawtooth_290.max_value = 56
temp_sawtooth_290.duration = 2000
animation.global('disco_pulse_').pos = temp_sawtooth_290 # Fast movement
# Start all animations
# Start all animations/sequences
if global.contains('sequence_disco_base')

View File

@ -2,14 +2,14 @@
# Source: fire_flicker.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Fire Flicker - Realistic fire simulation
# # Warm colors with random flickering intensity
#
# strip length 60
# #strip length 60
#
# # Define fire palette from black to yellow
# palette fire_colors = [
@ -21,17 +21,17 @@
# ]
#
# # Create base fire animation with palette
# animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
# animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
#
# # Add flickering effect with random intensity changes
# fire_base.opacity = smooth(180, 255, 800ms)
# fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
#
# # Add subtle position variation for more realism
# pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
# color flicker_pattern = rich_palette(palette=fire_colors, cycle_period=2s, transition_type=LINEAR, brightness=255)
# animation fire_flicker = twinkle_animation(
# flicker_pattern, # color source
# 12, # density (number of flickers)
# 200ms # twinkle speed (flicker duration)
# color=flicker_pattern, # color source
# density=12, # density (number of flickers)
# twinkle_speed=200ms # twinkle speed (flicker duration)
# )
# fire_flicker.priority = 10
#
@ -43,16 +43,34 @@ import animation
# Fire Flicker - Realistic fire simulation
# Warm colors with random flickering intensity
var engine = animation.init_strip(60)
#strip length 60
# Define fire palette from black to yellow
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF4500" "FFFFFF00")
# Create base fire animation with palette
var fire_base_ = animation.rich_palette_animation(animation.global('fire_colors_', 'fire_colors'), 3000, animation.global('linear_', 'linear'), 255)
var fire_base_ = animation.rich_palette_animation(engine)
fire_base_.palette = animation.global('fire_colors_', 'fire_colors')
fire_base_.cycle_period = 3000
fire_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
fire_base_.brightness = 255
# Add flickering effect with random intensity changes
animation.global('fire_base_').opacity = animation.smooth(180, 255, 800)
var temp_smooth_89 = animation.smooth(engine)
temp_smooth_89.min_value = 180
temp_smooth_89.max_value = 255
temp_smooth_89.duration = 800
animation.global('fire_base_').opacity = temp_smooth_89
# Add subtle position variation for more realism
var flicker_pattern_ = animation.rich_palette_color_provider(animation.global('fire_colors_', 'fire_colors'), 2000, animation.global('linear_', 'linear'), 255)
var fire_flicker_ = animation.twinkle_animation(animation.global('flicker_pattern_', 'flicker_pattern'), 12, 200)
var flicker_pattern_ = animation.rich_palette(engine)
flicker_pattern_.palette = animation.global('fire_colors_', 'fire_colors')
flicker_pattern_.cycle_period = 2000
flicker_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
flicker_pattern_.brightness = 255
var fire_flicker_ = animation.twinkle_animation(engine)
fire_flicker_.color = animation.global('flicker_pattern_', 'flicker_pattern')
fire_flicker_.density = 12
fire_flicker_.twinkle_speed = 200 # twinkle speed (flicker duration)
animation.global('fire_flicker_').priority = 10
# Start both animations
# Start all animations/sequences

View File

@ -2,45 +2,45 @@
# Source: heartbeat_pulse.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Heartbeat Pulse - Rhythmic double pulse
# # Red pulsing like a heartbeat
#
# strip length 60
# #strip length 60
#
# # Dark background
# color heart_bg = 0x110000
# animation background = solid(heart_bg)
# animation background = solid(color=heart_bg)
#
# # Define heartbeat timing - double pulse pattern
# # Define heartbeat timing - double pulse animation
# # First pulse (stronger)
# animation heartbeat1 = solid(0xFF0000) # Bright red
# heartbeat1.opacity = square(0, 255, 150ms, 20) # Quick strong pulse
# animation heartbeat1 = solid(color=0xFF0000) # Bright red
# heartbeat1.opacity = square(min_value=0, max_value=255, duration=150ms, duty_cycle=20) # Quick strong pulse
# heartbeat1.priority = 10
#
# # Second pulse (weaker, slightly delayed)
# animation heartbeat2 = solid(0xCC0000) # Slightly dimmer red
# animation heartbeat2 = solid(color=0xCC0000) # Slightly dimmer red
# # Delay the second pulse by adjusting the square wave phase
# heartbeat2.opacity = square(0, 180, 150ms, 15) # Weaker pulse
# heartbeat2.opacity = square(min_value=0, max_value=180, duration=150ms, duty_cycle=15) # Weaker pulse
# heartbeat2.priority = 8
#
# # Add subtle glow effect
# animation heart_glow = solid(0x660000) # Dim red glow
# heart_glow.opacity = smooth(30, 100, 1s) # Gentle breathing glow
# animation heart_glow = solid(color=0x660000) # Dim red glow
# heart_glow.opacity = smooth(min_value=30, max_value=100, duration=1s) # Gentle breathing glow
# heart_glow.priority = 5
#
# # Add center pulse for emphasis
# animation center_pulse = pulse_position_animation(
# 0xFFFFFF, # White center
# 30, # center of strip
# 4, # small center
# 2 # soft edges
# animation center_pulse = beacon_animation(
# color=0xFFFFFF, # White center
# pos=30, # center of strip
# beacon_size=4, # small center
# slew_size=2 # soft edges
# )
# center_pulse.priority = 20
# center_pulse.opacity = square(0, 200, 100ms, 10) # Quick white flash
# center_pulse.opacity = square(min_value=0, max_value=200, duration=100ms, duty_cycle=10) # Quick white flash
#
# # Start all animations
# run background
@ -53,28 +53,61 @@ import animation
# Heartbeat Pulse - Rhythmic double pulse
# Red pulsing like a heartbeat
var engine = animation.init_strip(60)
#strip length 60
# Dark background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var heart_bg_ = 0xFF110000
var background_ = animation.solid(animation.global('heart_bg_', 'heart_bg'))
# Define heartbeat timing - double pulse pattern
var background_ = animation.solid(engine)
background_.color = animation.global('heart_bg_', 'heart_bg')
# Define heartbeat timing - double pulse animation
# First pulse (stronger)
var heartbeat1_ = animation.solid(0xFFFF0000) # Bright red
animation.global('heartbeat1_').opacity = animation.square(0, 255, 150, 20) # Quick strong pulse
var heartbeat1_ = animation.solid(engine)
heartbeat1_.color = 0xFFFF0000
# Bright red
var temp_square_46 = animation.square(engine)
temp_square_46.min_value = 0
temp_square_46.max_value = 255
temp_square_46.duration = 150
temp_square_46.duty_cycle = 20
animation.global('heartbeat1_').opacity = temp_square_46 # Quick strong pulse
animation.global('heartbeat1_').priority = 10
# Second pulse (weaker, slightly delayed)
var heartbeat2_ = animation.solid(0xFFCC0000) # Slightly dimmer red
var heartbeat2_ = animation.solid(engine)
heartbeat2_.color = 0xFFCC0000
# Slightly dimmer red
# Delay the second pulse by adjusting the square wave phase
animation.global('heartbeat2_').opacity = animation.square(0, 180, 150, 15) # Weaker pulse
var temp_square_92 = animation.square(engine)
temp_square_92.min_value = 0
temp_square_92.max_value = 180
temp_square_92.duration = 150
temp_square_92.duty_cycle = 15
animation.global('heartbeat2_').opacity = temp_square_92 # Weaker pulse
animation.global('heartbeat2_').priority = 8
# Add subtle glow effect
var heart_glow_ = animation.solid(0xFF660000) # Dim red glow
animation.global('heart_glow_').opacity = animation.smooth(30, 100, 1000) # Gentle breathing glow
var heart_glow_ = animation.solid(engine)
heart_glow_.color = 0xFF660000
# Dim red glow
var temp_smooth_136 = animation.smooth(engine)
temp_smooth_136.min_value = 30
temp_smooth_136.max_value = 100
temp_smooth_136.duration = 1000
animation.global('heart_glow_').opacity = temp_smooth_136 # Gentle breathing glow
animation.global('heart_glow_').priority = 5
# Add center pulse for emphasis
var center_pulse_ = animation.pulse_position_animation(0xFFFFFFFF, 30, 4, 2)
var center_pulse_ = animation.beacon_animation(engine)
center_pulse_.color = 0xFFFFFFFF
center_pulse_.pos = 30
center_pulse_.beacon_size = 4
center_pulse_.slew_size = 2 # soft edges
animation.global('center_pulse_').priority = 20
animation.global('center_pulse_').opacity = animation.square(0, 200, 100, 10) # Quick white flash
var temp_square_202 = animation.square(engine)
temp_square_202.min_value = 0
temp_square_202.max_value = 200
temp_square_202.duration = 100
temp_square_202.duty_cycle = 10
animation.global('center_pulse_').opacity = temp_square_202 # Quick white flash
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')

View File

@ -2,14 +2,14 @@
# Source: lava_lamp.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Lava Lamp - Slow flowing warm colors
# # Organic movement like a lava lamp
#
# strip length 60
# #strip length 60
#
# # Define lava colors (warm oranges and reds)
# palette lava_colors = [
@ -21,45 +21,45 @@
# ]
#
# # Base lava animation - very slow color changes
# animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 180)
# animation lava_base = rich_palette_animation(palette=lava_colors, cycle_period=15s, transition_type=SINE, brightness=180)
#
# # Add slow-moving lava blobs
# pattern blob1_pattern = rich_palette_color_provider(lava_colors, 12s, smooth, 255)
# animation lava_blob1 = pulse_position_animation(
# blob1_pattern, # color source
# 9, # initial position
# 18, # large blob
# 12 # very soft edges
# color blob1_pattern = rich_palette(palette=lava_colors, cycle_period=12s, transition_type=SINE, brightness=255)
# animation lava_blob1 = beacon_animation(
# color=blob1_pattern, # color source
# pos=9, # initial position
# beacon_size=18, # large blob
# slew_size=12 # very soft edges
# )
# lava_blob1.priority = 10
# lava_blob1.pos = smooth(9, 51, 20s) # Very slow movement
# lava_blob1.pos = smooth(min_value=9, max_value=51, duration=20s) # Very slow movement
#
# pattern blob2_pattern = rich_palette_color_provider(lava_colors, 10s, smooth, 220)
# animation lava_blob2 = pulse_position_animation(
# blob2_pattern, # color source
# 46, # initial position
# 14, # medium blob
# 10 # soft edges
# color blob2_pattern = rich_palette(palette=lava_colors, cycle_period=10s, transition_type=SINE, brightness=220)
# animation lava_blob2 = beacon_animation(
# color=blob2_pattern, # color source
# pos=46, # initial position
# beacon_size=14, # medium blob
# slew_size=10 # soft edges
# )
# lava_blob2.priority = 8
# lava_blob2.pos = smooth(46, 14, 25s) # Opposite direction, slower
# lava_blob2.pos = smooth(min_value=46, max_value=14, duration=25s) # Opposite direction, slower
#
# pattern blob3_pattern = rich_palette_color_provider(lava_colors, 8s, smooth, 200)
# animation lava_blob3 = pulse_position_animation(
# blob3_pattern, # color source
# 25, # initial position
# 10, # smaller blob
# 8 # soft edges
# color blob3_pattern = rich_palette(palette=lava_colors, cycle_period=8s, transition_type=SINE, brightness=200)
# animation lava_blob3 = beacon_animation(
# color=blob3_pattern, # color source
# pos=25, # initial position
# beacon_size=10, # smaller blob
# slew_size=8 # soft edges
# )
# lava_blob3.priority = 6
# lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
# lava_blob3.pos = smooth(min_value=25, max_value=35, duration=18s) # Small movement range
#
# # Add subtle heat shimmer effect
# pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
# color shimmer_pattern = rich_palette(palette=lava_colors, cycle_period=6s, transition_type=SINE, brightness=255)
# animation heat_shimmer = twinkle_animation(
# shimmer_pattern, # color source
# 6, # density (shimmer points)
# 1.5s # twinkle speed (slow shimmer)
# color=shimmer_pattern, # color source
# density=6, # density (shimmer points)
# twinkle_speed=1.5s # twinkle speed (slow shimmer)
# )
# heat_shimmer.priority = 15
#
@ -74,27 +74,77 @@ import animation
# Lava Lamp - Slow flowing warm colors
# Organic movement like a lava lamp
var engine = animation.init_strip(60)
#strip length 60
# Define lava colors (warm oranges and reds)
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var lava_colors_ = bytes("00330000" "40660000" "80CC3300" "C0FF6600" "FFFFAA00")
# Base lava animation - very slow color changes
var lava_base_ = animation.rich_palette_animation(animation.global('lava_colors_', 'lava_colors'), 15000, animation.global('smooth_', 'smooth'), 180)
var lava_base_ = animation.rich_palette_animation(engine)
lava_base_.palette = animation.global('lava_colors_', 'lava_colors')
lava_base_.cycle_period = 15000
lava_base_.transition_type = animation.global('SINE_', 'SINE')
lava_base_.brightness = 180
# Add slow-moving lava blobs
var blob1_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 12000, animation.global('smooth_', 'smooth'), 255)
var lava_blob1_ = animation.pulse_position_animation(animation.global('blob1_pattern_', 'blob1_pattern'), 9, 18, 12)
var blob1_pattern_ = animation.rich_palette(engine)
blob1_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob1_pattern_.cycle_period = 12000
blob1_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob1_pattern_.brightness = 255
var lava_blob1_ = animation.beacon_animation(engine)
lava_blob1_.color = animation.global('blob1_pattern_', 'blob1_pattern')
lava_blob1_.pos = 9
lava_blob1_.beacon_size = 18
lava_blob1_.slew_size = 12 # very soft edges
animation.global('lava_blob1_').priority = 10
animation.global('lava_blob1_').pos = animation.smooth(9, 51, 20000) # Very slow movement
var blob2_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 10000, animation.global('smooth_', 'smooth'), 220)
var lava_blob2_ = animation.pulse_position_animation(animation.global('blob2_pattern_', 'blob2_pattern'), 46, 14, 10)
var temp_smooth_148 = animation.smooth(engine)
temp_smooth_148.min_value = 9
temp_smooth_148.max_value = 51
temp_smooth_148.duration = 20000
animation.global('lava_blob1_').pos = temp_smooth_148 # Very slow movement
var blob2_pattern_ = animation.rich_palette(engine)
blob2_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob2_pattern_.cycle_period = 10000
blob2_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob2_pattern_.brightness = 220
var lava_blob2_ = animation.beacon_animation(engine)
lava_blob2_.color = animation.global('blob2_pattern_', 'blob2_pattern')
lava_blob2_.pos = 46
lava_blob2_.beacon_size = 14
lava_blob2_.slew_size = 10 # soft edges
animation.global('lava_blob2_').priority = 8
animation.global('lava_blob2_').pos = animation.smooth(46, 14, 25000) # Opposite direction, slower
var blob3_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 8000, animation.global('smooth_', 'smooth'), 200)
var lava_blob3_ = animation.pulse_position_animation(animation.global('blob3_pattern_', 'blob3_pattern'), 25, 10, 8)
var temp_smooth_228 = animation.smooth(engine)
temp_smooth_228.min_value = 46
temp_smooth_228.max_value = 14
temp_smooth_228.duration = 25000
animation.global('lava_blob2_').pos = temp_smooth_228 # Opposite direction, slower
var blob3_pattern_ = animation.rich_palette(engine)
blob3_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
blob3_pattern_.cycle_period = 8000
blob3_pattern_.transition_type = animation.global('SINE_', 'SINE')
blob3_pattern_.brightness = 200
var lava_blob3_ = animation.beacon_animation(engine)
lava_blob3_.color = animation.global('blob3_pattern_', 'blob3_pattern')
lava_blob3_.pos = 25
lava_blob3_.beacon_size = 10
lava_blob3_.slew_size = 8 # soft edges
animation.global('lava_blob3_').priority = 6
animation.global('lava_blob3_').pos = animation.smooth(25, 35, 18000) # Small movement range
var temp_smooth_308 = animation.smooth(engine)
temp_smooth_308.min_value = 25
temp_smooth_308.max_value = 35
temp_smooth_308.duration = 18000
animation.global('lava_blob3_').pos = temp_smooth_308 # Small movement range
# Add subtle heat shimmer effect
var shimmer_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 6000, animation.global('smooth_', 'smooth'), 255)
var heat_shimmer_ = animation.twinkle_animation(animation.global('shimmer_pattern_', 'shimmer_pattern'), 6, 1500)
var shimmer_pattern_ = animation.rich_palette(engine)
shimmer_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
shimmer_pattern_.cycle_period = 6000
shimmer_pattern_.transition_type = animation.global('SINE_', 'SINE')
shimmer_pattern_.brightness = 255
var heat_shimmer_ = animation.twinkle_animation(engine)
heat_shimmer_.color = animation.global('shimmer_pattern_', 'shimmer_pattern')
heat_shimmer_.density = 6
heat_shimmer_.twinkle_speed = 1500 # twinkle speed (slow shimmer)
animation.global('heat_shimmer_').priority = 15
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: lightning_storm.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Lightning Storm - Random lightning flashes
# # Dark stormy background with bright lightning
#
# strip length 60
# #strip length 60
#
# # Dark stormy background with subtle purple/blue
# palette storm_colors = [
@ -18,33 +18,33 @@
# (255, 0x220033) # Slightly lighter purple
# ]
#
# animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
# animation storm_bg = rich_palette_animation(palette=storm_colors, cycle_period=12s, transition_type=SINE, brightness=100)
#
# # Random lightning flashes - full strip
# animation lightning_main = solid(0xFFFFFF) # Bright white
# lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
# animation lightning_main = solid(color=0xFFFFFF) # Bright white
# lightning_main.opacity = square(min_value=0, max_value=255, duration=80ms, duty_cycle=3) # Quick bright flashes
# lightning_main.priority = 20
#
# # Secondary lightning - partial strip
# animation lightning_partial = pulse_position_animation(
# 0xFFFFAA, # Slightly yellow white
# 30, # center position
# 20, # covers part of strip
# 5 # soft edges
# animation lightning_partial = beacon_animation(
# color=0xFFFFAA, # Slightly yellow white
# pos=30, # center position
# beacon_size=20, # covers part of strip
# slew_size=5 # soft edges
# )
# lightning_partial.priority = 15
# lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
# lightning_partial.opacity = square(min_value=0, max_value=200, duration=120ms, duty_cycle=4) # Different timing
#
# # Add blue afterglow
# animation afterglow = solid(0x4444FF) # Blue glow
# afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
# animation afterglow = solid(color=0x4444FF) # Blue glow
# afterglow.opacity = square(min_value=0, max_value=80, duration=200ms, duty_cycle=8) # Longer, dimmer glow
# afterglow.priority = 10
#
# # Distant thunder (dim flashes)
# animation distant_flash = twinkle_animation(
# 0x666699, # Dim blue-white
# 4, # density (few flashes)
# 300ms # twinkle speed (medium duration)
# color=0x666699, # Dim blue-white
# density=4, # density (few flashes)
# twinkle_speed=300ms # twinkle speed (medium duration)
# )
# distant_flash.priority = 5
#
@ -59,24 +59,57 @@ import animation
# Lightning Storm - Random lightning flashes
# Dark stormy background with bright lightning
var engine = animation.init_strip(60)
#strip length 60
# Dark stormy background with subtle purple/blue
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var storm_colors_ = bytes("00000011" "80110022" "FF220033")
var storm_bg_ = animation.rich_palette_animation(animation.global('storm_colors_', 'storm_colors'), 12000, animation.global('smooth_', 'smooth'), 100)
var storm_bg_ = animation.rich_palette_animation(engine)
storm_bg_.palette = animation.global('storm_colors_', 'storm_colors')
storm_bg_.cycle_period = 12000
storm_bg_.transition_type = animation.global('SINE_', 'SINE')
storm_bg_.brightness = 100
# Random lightning flashes - full strip
var lightning_main_ = animation.solid(0xFFFFFFFF) # Bright white
animation.global('lightning_main_').opacity = animation.square(0, 255, 80, 3) # Quick bright flashes
var lightning_main_ = animation.solid(engine)
lightning_main_.color = 0xFFFFFFFF
# Bright white
var temp_square_82 = animation.square(engine)
temp_square_82.min_value = 0
temp_square_82.max_value = 255
temp_square_82.duration = 80
temp_square_82.duty_cycle = 3
animation.global('lightning_main_').opacity = temp_square_82 # Quick bright flashes
animation.global('lightning_main_').priority = 20
# Secondary lightning - partial strip
var lightning_partial_ = animation.pulse_position_animation(0xFFFFFFAA, 30, 20, 5)
var lightning_partial_ = animation.beacon_animation(engine)
lightning_partial_.color = 0xFFFFFFAA
lightning_partial_.pos = 30
lightning_partial_.beacon_size = 20
lightning_partial_.slew_size = 5 # soft edges
animation.global('lightning_partial_').priority = 15
animation.global('lightning_partial_').opacity = animation.square(0, 200, 120, 4) # Different timing
var temp_square_152 = animation.square(engine)
temp_square_152.min_value = 0
temp_square_152.max_value = 200
temp_square_152.duration = 120
temp_square_152.duty_cycle = 4
animation.global('lightning_partial_').opacity = temp_square_152 # Different timing
# Add blue afterglow
var afterglow_ = animation.solid(0xFF4444FF) # Blue glow
animation.global('afterglow_').opacity = animation.square(0, 80, 200, 8) # Longer, dimmer glow
var afterglow_ = animation.solid(engine)
afterglow_.color = 0xFF4444FF
# Blue glow
var temp_square_190 = animation.square(engine)
temp_square_190.min_value = 0
temp_square_190.max_value = 80
temp_square_190.duration = 200
temp_square_190.duty_cycle = 8
animation.global('afterglow_').opacity = temp_square_190 # Longer, dimmer glow
animation.global('afterglow_').priority = 10
# Distant thunder (dim flashes)
var distant_flash_ = animation.twinkle_animation(0xFF666699, 4, 300)
var distant_flash_ = animation.twinkle_animation(engine)
distant_flash_.color = 0xFF666699
distant_flash_.density = 4
distant_flash_.twinkle_speed = 300 # twinkle speed (medium duration)
animation.global('distant_flash_').priority = 5
# Start all animations
# Start all animations/sequences

View File

@ -2,18 +2,18 @@
# Source: matrix_rain.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Matrix Rain - Digital rain effect
# # Green cascading code like The Matrix
#
# strip length 60
# #strip length 60
#
# # Dark background
# color matrix_bg = 0x000000
# animation background = solid(matrix_bg)
# animation background = solid(color=matrix_bg)
#
# # Define matrix green palette
# palette matrix_greens = [
@ -25,35 +25,35 @@
# ]
#
# # Create multiple cascading streams
# pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
# color stream1_pattern = rich_palette(palette=matrix_greens, cycle_period=2s, transition_type=LINEAR, brightness=255)
# animation stream1 = comet_animation(
# stream1_pattern, # color source
# 15, # long tail
# 1.5s # speed
# color=stream1_pattern, # color source
# tail_length=15, # long tail
# speed=1.5s # speed
# )
# stream1.priority = 10
#
# pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
# color stream2_pattern = rich_palette(palette=matrix_greens, cycle_period=1.8s, transition_type=LINEAR, brightness=200)
# animation stream2 = comet_animation(
# stream2_pattern, # color source
# 12, # medium tail
# 2.2s # different speed
# color=stream2_pattern, # color source
# tail_length=12, # medium tail
# speed=2.2s # different speed
# )
# stream2.priority = 8
#
# pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
# color stream3_pattern = rich_palette(palette=matrix_greens, cycle_period=2.5s, transition_type=LINEAR, brightness=180)
# animation stream3 = comet_animation(
# stream3_pattern, # color source
# 10, # shorter tail
# 1.8s # another speed
# color=stream3_pattern, # color source
# tail_length=10, # shorter tail
# speed=1.8s # another speed
# )
# stream3.priority = 6
#
# # Add random bright flashes (like code highlights)
# animation code_flash = twinkle_animation(
# 0x00FFAA, # Bright cyan-green
# 3, # density (few flashes)
# 150ms # twinkle speed (quick flash)
# color=0x00FFAA, # Bright cyan-green
# density=3, # density (few flashes)
# twinkle_speed=150ms # twinkle speed (quick flash)
# )
# code_flash.priority = 20
#
@ -68,24 +68,52 @@ import animation
# Matrix Rain - Digital rain effect
# Green cascading code like The Matrix
var engine = animation.init_strip(60)
#strip length 60
# Dark background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var matrix_bg_ = 0xFF000000
var background_ = animation.solid(animation.global('matrix_bg_', 'matrix_bg'))
var background_ = animation.solid(engine)
background_.color = animation.global('matrix_bg_', 'matrix_bg')
# Define matrix green palette
var matrix_greens_ = bytes("00000000" "40003300" "80006600" "C000AA00" "FF00FF00")
# Create multiple cascading streams
var stream1_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 2000, animation.global('linear_', 'linear'), 255)
var stream1_ = animation.comet_animation(animation.global('stream1_pattern_', 'stream1_pattern'), 15, 1500)
var stream1_pattern_ = animation.rich_palette(engine)
stream1_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream1_pattern_.cycle_period = 2000
stream1_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream1_pattern_.brightness = 255
var stream1_ = animation.comet_animation(engine)
stream1_.color = animation.global('stream1_pattern_', 'stream1_pattern')
stream1_.tail_length = 15
stream1_.speed = 1500 # speed
animation.global('stream1_').priority = 10
var stream2_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 1800, animation.global('linear_', 'linear'), 200)
var stream2_ = animation.comet_animation(animation.global('stream2_pattern_', 'stream2_pattern'), 12, 2200)
var stream2_pattern_ = animation.rich_palette(engine)
stream2_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream2_pattern_.cycle_period = 1800
stream2_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream2_pattern_.brightness = 200
var stream2_ = animation.comet_animation(engine)
stream2_.color = animation.global('stream2_pattern_', 'stream2_pattern')
stream2_.tail_length = 12
stream2_.speed = 2200 # different speed
animation.global('stream2_').priority = 8
var stream3_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 2500, animation.global('linear_', 'linear'), 180)
var stream3_ = animation.comet_animation(animation.global('stream3_pattern_', 'stream3_pattern'), 10, 1800)
var stream3_pattern_ = animation.rich_palette(engine)
stream3_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
stream3_pattern_.cycle_period = 2500
stream3_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
stream3_pattern_.brightness = 180
var stream3_ = animation.comet_animation(engine)
stream3_.color = animation.global('stream3_pattern_', 'stream3_pattern')
stream3_.tail_length = 10
stream3_.speed = 1800 # another speed
animation.global('stream3_').priority = 6
# Add random bright flashes (like code highlights)
var code_flash_ = animation.twinkle_animation(0xFF00FFAA, 3, 150)
var code_flash_ = animation.twinkle_animation(engine)
code_flash_.color = 0xFF00FFAA
code_flash_.density = 3
code_flash_.twinkle_speed = 150 # twinkle speed (quick flash)
animation.global('code_flash_').priority = 20
# Start all animations
# Start all animations/sequences

View File

@ -2,61 +2,61 @@
# Source: meteor_shower.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Meteor Shower - Multiple meteors with trails
# # Fast moving bright objects with fading trails
#
# strip length 60
# #strip length 60
#
# # Dark space background
# color space_bg = 0x000011
# animation background = solid(space_bg)
# animation background = solid(color=space_bg)
#
# # Multiple meteors with different speeds and colors
# animation meteor1 = comet_animation(
# 0xFFFFFF, # Bright white
# 12, # long trail
# 1.5s # fast speed
# color=0xFFFFFF, # Bright white
# tail_length=12, # long trail
# speed=1.5s # fast speed
# )
# meteor1.priority = 15
#
# animation meteor2 = comet_animation(
# 0xFFAA00, # Orange
# 10, # medium trail
# 2s # medium speed
# color=0xFFAA00, # Orange
# tail_length=10, # medium trail
# speed=2s # medium speed
# )
# meteor2.priority = 12
#
# animation meteor3 = comet_animation(
# 0xAAAAFF, # Blue-white
# 8, # shorter trail
# 1.8s # fast speed
# color=0xAAAAFF, # Blue-white
# tail_length=8, # shorter trail
# speed=1.8s # fast speed
# )
# meteor3.priority = 10
#
# animation meteor4 = comet_animation(
# 0xFFAAAA, # Pink-white
# 14, # long trail
# 2.5s # slower speed
# color=0xFFAAAA, # Pink-white
# tail_length=14, # long trail
# speed=2.5s # slower speed
# )
# meteor4.priority = 8
#
# # Add distant stars
# animation stars = twinkle_animation(
# 0xCCCCCC, # Dim white
# 12, # density (many stars)
# 2s # twinkle speed (slow twinkle)
# color=0xCCCCCC, # Dim white
# density=12, # density (many stars)
# twinkle_speed=2s # twinkle speed (slow twinkle)
# )
# stars.priority = 5
#
# # Add occasional bright flash (meteor explosion)
# animation meteor_flash = twinkle_animation(
# 0xFFFFFF, # Bright white
# 1, # density (single flash)
# 100ms # twinkle speed (very quick)
# color=0xFFFFFF, # Bright white
# density=1, # density (single flash)
# twinkle_speed=100ms # twinkle speed (very quick)
# )
# meteor_flash.priority = 25
#
@ -73,24 +73,46 @@ import animation
# Meteor Shower - Multiple meteors with trails
# Fast moving bright objects with fading trails
var engine = animation.init_strip(60)
#strip length 60
# Dark space background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var space_bg_ = 0xFF000011
var background_ = animation.solid(animation.global('space_bg_', 'space_bg'))
var background_ = animation.solid(engine)
background_.color = animation.global('space_bg_', 'space_bg')
# Multiple meteors with different speeds and colors
var meteor1_ = animation.comet_animation(0xFFFFFFFF, 12, 1500)
var meteor1_ = animation.comet_animation(engine)
meteor1_.color = 0xFFFFFFFF
meteor1_.tail_length = 12
meteor1_.speed = 1500 # fast speed
animation.global('meteor1_').priority = 15
var meteor2_ = animation.comet_animation(0xFFFFAA00, 10, 2000)
var meteor2_ = animation.comet_animation(engine)
meteor2_.color = 0xFFFFAA00
meteor2_.tail_length = 10
meteor2_.speed = 2000 # medium speed
animation.global('meteor2_').priority = 12
var meteor3_ = animation.comet_animation(0xFFAAAAFF, 8, 1800)
var meteor3_ = animation.comet_animation(engine)
meteor3_.color = 0xFFAAAAFF
meteor3_.tail_length = 8
meteor3_.speed = 1800 # fast speed
animation.global('meteor3_').priority = 10
var meteor4_ = animation.comet_animation(0xFFFFAAAA, 14, 2500)
var meteor4_ = animation.comet_animation(engine)
meteor4_.color = 0xFFFFAAAA
meteor4_.tail_length = 14
meteor4_.speed = 2500 # slower speed
animation.global('meteor4_').priority = 8
# Add distant stars
var stars_ = animation.twinkle_animation(0xFFCCCCCC, 12, 2000)
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFCCCCCC
stars_.density = 12
stars_.twinkle_speed = 2000 # twinkle speed (slow twinkle)
animation.global('stars_').priority = 5
# Add occasional bright flash (meteor explosion)
var meteor_flash_ = animation.twinkle_animation(0xFFFFFFFF, 1, 100)
var meteor_flash_ = animation.twinkle_animation(engine)
meteor_flash_.color = 0xFFFFFFFF
meteor_flash_.density = 1
meteor_flash_.twinkle_speed = 100 # twinkle speed (very quick)
animation.global('meteor_flash_').priority = 25
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: neon_glow.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Neon Glow - Electric neon tube effect
# # Bright saturated colors with flickering
#
# strip length 60
# #strip length 60
#
# # Define neon colors
# palette neon_colors = [
@ -20,47 +20,47 @@
# ]
#
# # Main neon glow with color cycling
# animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
# animation neon_main = rich_palette_animation(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
#
# # Add electrical flickering
# neon_main.opacity = smooth(220, 255, 200ms)
# neon_main.opacity = smooth(min_value=220, max_value=255, duration=200ms)
#
# # Add occasional electrical surge
# animation neon_surge = solid(0xFFFFFF) # White surge
# neon_surge.opacity = square(0, 255, 50ms, 2) # Quick bright surges
# animation neon_surge = solid(color=0xFFFFFF) # White surge
# neon_surge.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=2) # Quick bright surges
# neon_surge.priority = 20
#
# # Add neon tube segments with gaps
# pattern segment_pattern = rich_palette_color_provider(neon_colors, 4s, linear, 255)
# animation segment1 = pulse_position_animation(
# segment_pattern, # color source
# 6, # position
# 12, # segment length
# 1 # sharp edges
# color segment_pattern = rich_palette(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
# animation segment1 = beacon_animation(
# color=segment_pattern, # color source
# pos=6, # position
# beacon_size=12, # segment length
# slew_size=1 # sharp edges
# )
# segment1.priority = 10
#
# animation segment2 = pulse_position_animation(
# segment_pattern, # color source
# 24, # position
# 12, # segment length
# 1 # sharp edges
# animation segment2 = beacon_animation(
# color=segment_pattern, # color source
# pos=24, # position
# beacon_size=12, # segment length
# slew_size=1 # sharp edges
# )
# segment2.priority = 10
#
# animation segment3 = pulse_position_animation(
# segment_pattern, # color source
# 42, # position
# 12, # segment length
# 1 # sharp edges
# animation segment3 = beacon_animation(
# color=segment_pattern, # color source
# pos=42, # position
# beacon_size=12, # segment length
# slew_size=1 # sharp edges
# )
# segment3.priority = 10
#
# # Add electrical arcing between segments
# animation arc_sparkles = twinkle_animation(
# 0xAAAAFF, # Electric blue
# 4, # density (few arcs)
# 100ms # twinkle speed (quick arcs)
# color=0xAAAAFF, # Electric blue
# density=4, # density (few arcs)
# twinkle_speed=100ms # twinkle speed (quick arcs)
# )
# arc_sparkles.priority = 15
#
@ -76,27 +76,64 @@ import animation
# Neon Glow - Electric neon tube effect
# Bright saturated colors with flickering
var engine = animation.init_strip(60)
#strip length 60
# Define neon colors
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var neon_colors_ = bytes("00FF0080" "5500FF80" "AA8000FF" "FFFF8000")
# Main neon glow with color cycling
var neon_main_ = animation.rich_palette_animation(animation.global('neon_colors_', 'neon_colors'), 4000, animation.global('linear_', 'linear'), 255)
var neon_main_ = animation.rich_palette_animation(engine)
neon_main_.palette = animation.global('neon_colors_', 'neon_colors')
neon_main_.cycle_period = 4000
neon_main_.transition_type = animation.global('LINEAR_', 'LINEAR')
neon_main_.brightness = 255
# Add electrical flickering
animation.global('neon_main_').opacity = animation.smooth(220, 255, 200)
var temp_smooth_81 = animation.smooth(engine)
temp_smooth_81.min_value = 220
temp_smooth_81.max_value = 255
temp_smooth_81.duration = 200
animation.global('neon_main_').opacity = temp_smooth_81
# Add occasional electrical surge
var neon_surge_ = animation.solid(0xFFFFFFFF) # White surge
animation.global('neon_surge_').opacity = animation.square(0, 255, 50, 2) # Quick bright surges
var neon_surge_ = animation.solid(engine)
neon_surge_.color = 0xFFFFFFFF
# White surge
var temp_square_114 = animation.square(engine)
temp_square_114.min_value = 0
temp_square_114.max_value = 255
temp_square_114.duration = 50
temp_square_114.duty_cycle = 2
animation.global('neon_surge_').opacity = temp_square_114 # Quick bright surges
animation.global('neon_surge_').priority = 20
# Add neon tube segments with gaps
var segment_pattern_ = animation.rich_palette_color_provider(animation.global('neon_colors_', 'neon_colors'), 4000, animation.global('linear_', 'linear'), 255)
var segment1_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 6, 12, 1)
var segment_pattern_ = animation.rich_palette(engine)
segment_pattern_.palette = animation.global('neon_colors_', 'neon_colors')
segment_pattern_.cycle_period = 4000
segment_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
segment_pattern_.brightness = 255
var segment1_ = animation.beacon_animation(engine)
segment1_.color = animation.global('segment_pattern_', 'segment_pattern')
segment1_.pos = 6
segment1_.beacon_size = 12
segment1_.slew_size = 1 # sharp edges
animation.global('segment1_').priority = 10
var segment2_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 24, 12, 1)
var segment2_ = animation.beacon_animation(engine)
segment2_.color = animation.global('segment_pattern_', 'segment_pattern')
segment2_.pos = 24
segment2_.beacon_size = 12
segment2_.slew_size = 1 # sharp edges
animation.global('segment2_').priority = 10
var segment3_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 42, 12, 1)
var segment3_ = animation.beacon_animation(engine)
segment3_.color = animation.global('segment_pattern_', 'segment_pattern')
segment3_.pos = 42
segment3_.beacon_size = 12
segment3_.slew_size = 1 # sharp edges
animation.global('segment3_').priority = 10
# Add electrical arcing between segments
var arc_sparkles_ = animation.twinkle_animation(0xFFAAAAFF, 4, 100)
var arc_sparkles_ = animation.twinkle_animation(engine)
arc_sparkles_.color = 0xFFAAAAFF
arc_sparkles_.density = 4
arc_sparkles_.twinkle_speed = 100 # twinkle speed (quick arcs)
animation.global('arc_sparkles_').priority = 15
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: ocean_waves.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Ocean Waves - Blue-green wave simulation
# # Flowing water colors with wave motion
#
# strip length 60
# #strip length 60
#
# # Define ocean color palette
# palette ocean_colors = [
@ -21,34 +21,34 @@
# ]
#
# # Base ocean animation with slow color cycling
# animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 200)
# animation ocean_base = rich_palette_animation(palette=ocean_colors, cycle_period=8s, transition_type=SINE, brightness=200)
#
# # Add wave motion with moving pulses
# pattern wave1_pattern = rich_palette_color_provider(ocean_colors, 6s, smooth, 255)
# animation wave1 = pulse_position_animation(
# wave1_pattern, # color source
# 0, # initial position
# 12, # wave width
# 6 # soft edges
# color wave1_pattern = rich_palette(palette=ocean_colors, cycle_period=6s, transition_type=SINE, brightness=255)
# animation wave1 = beacon_animation(
# color=wave1_pattern, # color source
# pos=0, # initial position
# beacon_size=12, # wave width
# slew_size=6 # soft edges
# )
# wave1.priority = 10
# wave1.pos = sawtooth(0, 48, 5s) # 60-12 = 48
# wave1.pos = sawtooth(min_value=0, max_value=48, duration=5s) # 60-12 = 48
#
# pattern wave2_pattern = rich_palette_color_provider(ocean_colors, 4s, smooth, 180)
# animation wave2 = pulse_position_animation(
# wave2_pattern, # color source
# 52, # initial position
# 8, # smaller wave
# 4 # soft edges
# color wave2_pattern = rich_palette(palette=ocean_colors, cycle_period=4s, transition_type=SINE, brightness=180)
# animation wave2 = beacon_animation(
# color=wave2_pattern, # color source
# pos=52, # initial position
# beacon_size=8, # smaller wave
# slew_size=4 # soft edges
# )
# wave2.priority = 8
# wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
# wave2.pos = sawtooth(min_value=52, max_value=8, duration=7s) # Opposite direction
#
# # Add foam sparkles
# animation foam = twinkle_animation(
# 0xFFFFFF, # White foam
# 6, # density (sparkle count)
# 300ms # twinkle speed (quick sparkles)
# color=0xFFFFFF, # White foam
# density=6, # density (sparkle count)
# twinkle_speed=300ms # twinkle speed (quick sparkles)
# )
# foam.priority = 15
#
@ -62,22 +62,56 @@ import animation
# Ocean Waves - Blue-green wave simulation
# Flowing water colors with wave motion
var engine = animation.init_strip(60)
#strip length 60
# Define ocean color palette
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var ocean_colors_ = bytes("00000080" "400040C0" "800080FF" "C040C0FF" "FF80FFFF")
# Base ocean animation with slow color cycling
var ocean_base_ = animation.rich_palette_animation(animation.global('ocean_colors_', 'ocean_colors'), 8000, animation.global('smooth_', 'smooth'), 200)
var ocean_base_ = animation.rich_palette_animation(engine)
ocean_base_.palette = animation.global('ocean_colors_', 'ocean_colors')
ocean_base_.cycle_period = 8000
ocean_base_.transition_type = animation.global('SINE_', 'SINE')
ocean_base_.brightness = 200
# Add wave motion with moving pulses
var wave1_pattern_ = animation.rich_palette_color_provider(animation.global('ocean_colors_', 'ocean_colors'), 6000, animation.global('smooth_', 'smooth'), 255)
var wave1_ = animation.pulse_position_animation(animation.global('wave1_pattern_', 'wave1_pattern'), 0, 12, 6)
var wave1_pattern_ = animation.rich_palette(engine)
wave1_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
wave1_pattern_.cycle_period = 6000
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave1_pattern_.brightness = 255
var wave1_ = animation.beacon_animation(engine)
wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern')
wave1_.pos = 0
wave1_.beacon_size = 12
wave1_.slew_size = 6 # soft edges
animation.global('wave1_').priority = 10
animation.global('wave1_').pos = animation.sawtooth(0, 48, 5000) # 60-12 = 48
var wave2_pattern_ = animation.rich_palette_color_provider(animation.global('ocean_colors_', 'ocean_colors'), 4000, animation.global('smooth_', 'smooth'), 180)
var wave2_ = animation.pulse_position_animation(animation.global('wave2_pattern_', 'wave2_pattern'), 52, 8, 4)
var temp_sawtooth_148 = animation.sawtooth(engine)
temp_sawtooth_148.min_value = 0
temp_sawtooth_148.max_value = 48
temp_sawtooth_148.duration = 5000
animation.global('wave1_').pos = temp_sawtooth_148 # 60-12 = 48
var wave2_pattern_ = animation.rich_palette(engine)
wave2_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
wave2_pattern_.cycle_period = 4000
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave2_pattern_.brightness = 180
var wave2_ = animation.beacon_animation(engine)
wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern')
wave2_.pos = 52
wave2_.beacon_size = 8
wave2_.slew_size = 4 # soft edges
animation.global('wave2_').priority = 8
animation.global('wave2_').pos = animation.sawtooth(52, 8, 7000) # Opposite direction
var temp_sawtooth_228 = animation.sawtooth(engine)
temp_sawtooth_228.min_value = 52
temp_sawtooth_228.max_value = 8
temp_sawtooth_228.duration = 7000
animation.global('wave2_').pos = temp_sawtooth_228 # Opposite direction
# Add foam sparkles
var foam_ = animation.twinkle_animation(0xFFFFFFFF, 6, 300)
var foam_ = animation.twinkle_animation(engine)
foam_.color = 0xFFFFFFFF
foam_.density = 6
foam_.twinkle_speed = 300 # twinkle speed (quick sparkles)
animation.global('foam_').priority = 15
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: palette_demo.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Palette Demo - Shows how to use custom palettes in DSL
# # This demonstrates the new palette syntax
#
# strip length 30
# #strip length 30
#
# # Define a fire palette
# palette fire_colors = [
@ -30,9 +30,9 @@
# ]
#
# # Create animations using the palettes
# animation fire_anim = rich_palette_animation(fire_colors, 5s)
# animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5s)
#
# animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
# animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
#
# # Sequence to show both palettes
# sequence palette_demo {
@ -51,14 +51,21 @@ import animation
# Palette Demo - Shows how to use custom palettes in DSL
# This demonstrates the new palette syntax
var engine = animation.init_strip(30)
#strip length 30
# Define a fire palette
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF8000" "FFFFFF00")
# Define an ocean palette
var ocean_colors_ = bytes("00000080" "400000FF" "8000FFFF" "C000FF80" "FF008000")
# Create animations using the palettes
var fire_anim_ = animation.rich_palette_animation(animation.global('fire_colors_', 'fire_colors'), 5000)
var ocean_anim_ = animation.rich_palette_animation(animation.global('ocean_colors_', 'ocean_colors'), 8000)
var fire_anim_ = animation.rich_palette_animation(engine)
fire_anim_.palette = animation.global('fire_colors_', 'fire_colors')
fire_anim_.cycle_period = 5000
var ocean_anim_ = animation.rich_palette_animation(engine)
ocean_anim_.palette = animation.global('ocean_colors_', 'ocean_colors')
ocean_anim_.cycle_period = 8000
# Sequence to show both palettes
def sequence_palette_demo()
var steps = []

View File

@ -2,14 +2,14 @@
# Source: palette_showcase.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Palette Showcase - Demonstrates all palette features
# # This example shows the full range of palette capabilities
#
# strip length 60
# #strip length 60
#
# # Example 1: Fire palette with hex colors
# palette fire_gradient = [
@ -52,13 +52,13 @@
# ]
#
# # Create animations using each palette
# animation fire_effect = rich_palette_animation(fire_gradient, 3s)
# animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
#
# animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
# animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
#
# animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
# animation aurora_lights = rich_palette_animation(palette=aurora_borealis, cycle_period=12s, transition_type=SINE, brightness=180)
#
# animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
# animation sunset_glow = rich_palette_animation(palette=sunset_sky, cycle_period=6s, transition_type=SINE, brightness=220)
#
# # Sequence to showcase all palettes
# sequence palette_showcase {
@ -92,8 +92,11 @@ import animation
# Palette Showcase - Demonstrates all palette features
# This example shows the full range of palette capabilities
var engine = animation.init_strip(60)
#strip length 60
# Example 1: Fire palette with hex colors
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var fire_gradient_ = bytes("00000000" "20330000" "40660000" "60CC0000" "80FF3300" "A0FF6600" "C0FF9900" "E0FFCC00" "FFFFFF00")
# Example 2: Ocean palette with named colors
var ocean_depths_ = bytes("00000000" "40000080" "800000FF" "C000FFFF" "FFFFFFFF")
@ -102,10 +105,24 @@ var aurora_borealis_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FF
# Example 4: Sunset palette mixing hex and named colors
var sunset_sky_ = bytes("00191970" "40800080" "80FF69B4" "C0FFA500" "FFFFFF00")
# Create animations using each palette
var fire_effect_ = animation.rich_palette_animation(animation.global('fire_gradient_', 'fire_gradient'), 3000)
var ocean_waves_ = animation.rich_palette_animation(animation.global('ocean_depths_', 'ocean_depths'), 8000, animation.global('smooth_', 'smooth'), 200)
var aurora_lights_ = animation.rich_palette_animation(animation.global('aurora_borealis_', 'aurora_borealis'), 12000, animation.global('smooth_', 'smooth'), 180)
var sunset_glow_ = animation.rich_palette_animation(animation.global('sunset_sky_', 'sunset_sky'), 6000, animation.global('smooth_', 'smooth'), 220)
var fire_effect_ = animation.rich_palette_animation(engine)
fire_effect_.palette = animation.global('fire_gradient_', 'fire_gradient')
fire_effect_.cycle_period = 3000
var ocean_waves_ = animation.rich_palette_animation(engine)
ocean_waves_.palette = animation.global('ocean_depths_', 'ocean_depths')
ocean_waves_.cycle_period = 8000
ocean_waves_.transition_type = animation.global('SINE_', 'SINE')
ocean_waves_.brightness = 200
var aurora_lights_ = animation.rich_palette_animation(engine)
aurora_lights_.palette = animation.global('aurora_borealis_', 'aurora_borealis')
aurora_lights_.cycle_period = 12000
aurora_lights_.transition_type = animation.global('SINE_', 'SINE')
aurora_lights_.brightness = 180
var sunset_glow_ = animation.rich_palette_animation(engine)
sunset_glow_.palette = animation.global('sunset_sky_', 'sunset_sky')
sunset_glow_.cycle_period = 6000
sunset_glow_.transition_type = animation.global('SINE_', 'SINE')
sunset_glow_.brightness = 220
# Sequence to showcase all palettes
def sequence_palette_showcase()
var steps = []

View File

@ -1,125 +0,0 @@
# Generated Berry code from Animation DSL
# Source: pattern_animation_demo.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Unified Pattern-Animation Demo
# # This DSL example demonstrates the new unified architecture where Animation extends Pattern
#
# strip length 30
#
# # UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
# # No more artificial distinction between patterns and animations
# animation solid_red = solid(red) # Animation: solid red (infinite duration)
# animation solid_blue = solid(blue) # Animation: solid blue (infinite duration)
# animation solid_green = solid(green) # Animation: solid green (infinite duration)
#
# # COMPOSITION: Animations can use other animations as base
# animation pulsing_red = pulse(solid_red, 0%, 100%, 2s) # Animation using animation
# animation pulsing_blue = pulse(solid_blue, 50%, 100%, 1s) # Animation using animation
# animation pulsing_green = pulse(solid_green, 20%, 80%, 3s) # Animation using animation
#
# # Set priorities (all animations inherit from Pattern)
# solid_red.priority = 0 # Base animation priority
# pulsing_red.priority = 10 # Higher priority
# pulsing_blue.priority = 20 # Even higher priority
# pulsing_green.priority = 5 # Medium priority
#
# # Set opacity (all animations inherit from Pattern)
# solid_red.opacity = 255 # Full opacity
# pulsing_red.opacity = 200 # Slightly dimmed
# pulsing_blue.opacity = 150 # More dimmed
#
# # RECURSIVE COMPOSITION: Animations can use other animations!
# # This creates infinitely composable effects
# animation complex_pulse = pulse(pulsing_red, 30%, 70%, 4s) # Pulse a pulsing animation!
#
# # Create a sequence that demonstrates the unified architecture
# sequence unified_demo {
# # All animations can be used directly in sequences
# play solid_red for 2s # Use solid animation
# wait 500ms
#
# # Composed animations work seamlessly
# play pulsing_red for 3s # Use pulse animation
# wait 500ms
#
# play pulsing_blue for 2s # Use another pulse animation
# wait 500ms
#
# # Show recursive composition - animation using animation
# play complex_pulse for 4s # Nested animation effect
# wait 500ms
#
# # Show that all animations support the same properties
# repeat 2 times:
# play solid_green for 1s # Animation with priority/opacity
# play pulsing_green for 2s # Animation with priority/opacity
# wait 500ms
# }
#
# # Run the demonstration
# run unified_demo
import animation
# Unified Pattern-Animation Demo
# This DSL example demonstrates the new unified architecture where Animation extends Pattern
var engine = animation.init_strip(30)
# UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
# No more artificial distinction between patterns and animations
var solid_red_ = animation.solid(0xFFFF0000) # Animation: solid red (infinite duration)
var solid_blue_ = animation.solid(0xFF0000FF) # Animation: solid blue (infinite duration)
var solid_green_ = animation.solid(0xFF008000) # Animation: solid green (infinite duration)
# COMPOSITION: Animations can use other animations as base
var pulsing_red_ = animation.pulse(animation.global('solid_red_', 'solid_red'), 0, 255, 2000) # Animation using animation
var pulsing_blue_ = animation.pulse(animation.global('solid_blue_', 'solid_blue'), 127, 255, 1000) # Animation using animation
var pulsing_green_ = animation.pulse(animation.global('solid_green_', 'solid_green'), 51, 204, 3000) # Animation using animation
# Set priorities (all animations inherit from Pattern)
animation.global('solid_red_').priority = 0 # Base animation priority
animation.global('pulsing_red_').priority = 10 # Higher priority
animation.global('pulsing_blue_').priority = 20 # Even higher priority
animation.global('pulsing_green_').priority = 5 # Medium priority
# Set opacity (all animations inherit from Pattern)
animation.global('solid_red_').opacity = 255 # Full opacity
animation.global('pulsing_red_').opacity = 200 # Slightly dimmed
animation.global('pulsing_blue_').opacity = 150 # More dimmed
# RECURSIVE COMPOSITION: Animations can use other animations!
# This creates infinitely composable effects
var complex_pulse_ = animation.pulse(animation.global('pulsing_red_', 'pulsing_red'), 76, 178, 4000) # Pulse a pulsing animation!
# Create a sequence that demonstrates the unified architecture
def sequence_unified_demo()
var steps = []
# All animations can be used directly in sequences
steps.push(animation.create_play_step(animation.global('solid_red_'), 2000)) # Use solid animation
steps.push(animation.create_wait_step(500))
# Composed animations work seamlessly
steps.push(animation.create_play_step(animation.global('pulsing_red_'), 3000)) # Use pulse animation
steps.push(animation.create_wait_step(500))
steps.push(animation.create_play_step(animation.global('pulsing_blue_'), 2000)) # Use another pulse animation
steps.push(animation.create_wait_step(500))
# Show recursive composition - animation using animation
steps.push(animation.create_play_step(animation.global('complex_pulse_'), 4000)) # Nested animation effect
steps.push(animation.create_wait_step(500))
# Show that all animations support the same properties
for repeat_i : 0..2-1
steps.push(animation.create_play_step(animation.global('solid_green_'), 1000)) # Animation with priority/opacity
steps.push(animation.create_play_step(animation.global('pulsing_green_'), 2000)) # Animation with priority/opacity
steps.push(animation.create_wait_step(500))
end
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Run the demonstration
# Start all animations/sequences
if global.contains('sequence_unified_demo')
var seq_manager = global.sequence_unified_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('unified_demo_'))
end
engine.start()

View File

@ -2,14 +2,14 @@
# Source: plasma_wave.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Plasma Wave - Smooth flowing plasma colors
# # Continuous color waves like plasma display
#
# strip length 60
# #strip length 60
#
# # Define plasma color palette with smooth transitions
# palette plasma_colors = [
@ -22,41 +22,41 @@
# ]
#
# # Base plasma animation with medium speed
# animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 200)
# animation plasma_base = rich_palette_animation(palette=plasma_colors, cycle_period=6s, transition_type=SINE, brightness=200)
#
# # Add multiple wave layers for complexity
# pattern wave1_pattern = rich_palette_color_provider(plasma_colors, 4s, smooth, 255)
# animation plasma_wave1 = pulse_position_animation(
# wave1_pattern, # color source
# 0, # initial position
# 20, # wide wave
# 10 # very smooth
# color wave1_pattern = rich_palette(palette=plasma_colors, cycle_period=4s, transition_type=SINE, brightness=255)
# animation plasma_wave1 = beacon_animation(
# color=wave1_pattern, # color source
# pos=0, # initial position
# beacon_size=20, # wide wave
# slew_size=10 # very smooth
# )
# plasma_wave1.priority = 10
# plasma_wave1.pos = smooth(0, 40, 8s)
# plasma_wave1.pos = smooth(min_value=0, max_value=40, duration=8s)
#
# pattern wave2_pattern = rich_palette_color_provider(plasma_colors, 5s, smooth, 180)
# animation plasma_wave2 = pulse_position_animation(
# wave2_pattern, # color source
# 45, # initial position
# 15, # medium wave
# 8 # smooth
# color wave2_pattern = rich_palette(palette=plasma_colors, cycle_period=5s, transition_type=SINE, brightness=180)
# animation plasma_wave2 = beacon_animation(
# color=wave2_pattern, # color source
# pos=45, # initial position
# beacon_size=15, # medium wave
# slew_size=8 # smooth
# )
# plasma_wave2.priority = 8
# plasma_wave2.pos = smooth(45, 15, 10s) # Opposite direction
# plasma_wave2.pos = smooth(min_value=45, max_value=15, duration=10s) # Opposite direction
#
# pattern wave3_pattern = rich_palette_color_provider(plasma_colors, 3s, smooth, 220)
# animation plasma_wave3 = pulse_position_animation(
# wave3_pattern, # color source
# 20, # initial position
# 12, # smaller wave
# 6 # smooth
# color wave3_pattern = rich_palette(palette=plasma_colors, cycle_period=3s, transition_type=SINE, brightness=220)
# animation plasma_wave3 = beacon_animation(
# color=wave3_pattern, # color source
# pos=20, # initial position
# beacon_size=12, # smaller wave
# slew_size=6 # smooth
# )
# plasma_wave3.priority = 12
# plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
# plasma_wave3.pos = smooth(min_value=20, max_value=50, duration=6s) # Different speed
#
# # Add subtle intensity variation
# plasma_base.opacity = smooth(150, 255, 12s)
# plasma_base.opacity = smooth(min_value=150, max_value=255, duration=12s)
#
# # Start all animations
# run plasma_base
@ -68,26 +68,73 @@ import animation
# Plasma Wave - Smooth flowing plasma colors
# Continuous color waves like plasma display
var engine = animation.init_strip(60)
#strip length 60
# Define plasma color palette with smooth transitions
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var plasma_colors_ = bytes("00FF0080" "33FF8000" "66FFFF00" "9980FF00" "CC00FF80" "FF0080FF")
# Base plasma animation with medium speed
var plasma_base_ = animation.rich_palette_animation(animation.global('plasma_colors_', 'plasma_colors'), 6000, animation.global('smooth_', 'smooth'), 200)
var plasma_base_ = animation.rich_palette_animation(engine)
plasma_base_.palette = animation.global('plasma_colors_', 'plasma_colors')
plasma_base_.cycle_period = 6000
plasma_base_.transition_type = animation.global('SINE_', 'SINE')
plasma_base_.brightness = 200
# Add multiple wave layers for complexity
var wave1_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 4000, animation.global('smooth_', 'smooth'), 255)
var plasma_wave1_ = animation.pulse_position_animation(animation.global('wave1_pattern_', 'wave1_pattern'), 0, 20, 10)
var wave1_pattern_ = animation.rich_palette(engine)
wave1_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave1_pattern_.cycle_period = 4000
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave1_pattern_.brightness = 255
var plasma_wave1_ = animation.beacon_animation(engine)
plasma_wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern')
plasma_wave1_.pos = 0
plasma_wave1_.beacon_size = 20
plasma_wave1_.slew_size = 10 # very smooth
animation.global('plasma_wave1_').priority = 10
animation.global('plasma_wave1_').pos = animation.smooth(0, 40, 8000)
var wave2_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 5000, animation.global('smooth_', 'smooth'), 180)
var plasma_wave2_ = animation.pulse_position_animation(animation.global('wave2_pattern_', 'wave2_pattern'), 45, 15, 8)
var temp_smooth_156 = animation.smooth(engine)
temp_smooth_156.min_value = 0
temp_smooth_156.max_value = 40
temp_smooth_156.duration = 8000
animation.global('plasma_wave1_').pos = temp_smooth_156
var wave2_pattern_ = animation.rich_palette(engine)
wave2_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave2_pattern_.cycle_period = 5000
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave2_pattern_.brightness = 180
var plasma_wave2_ = animation.beacon_animation(engine)
plasma_wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern')
plasma_wave2_.pos = 45
plasma_wave2_.beacon_size = 15
plasma_wave2_.slew_size = 8 # smooth
animation.global('plasma_wave2_').priority = 8
animation.global('plasma_wave2_').pos = animation.smooth(45, 15, 10000) # Opposite direction
var wave3_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 3000, animation.global('smooth_', 'smooth'), 220)
var plasma_wave3_ = animation.pulse_position_animation(animation.global('wave3_pattern_', 'wave3_pattern'), 20, 12, 6)
var temp_smooth_235 = animation.smooth(engine)
temp_smooth_235.min_value = 45
temp_smooth_235.max_value = 15
temp_smooth_235.duration = 10000
animation.global('plasma_wave2_').pos = temp_smooth_235 # Opposite direction
var wave3_pattern_ = animation.rich_palette(engine)
wave3_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
wave3_pattern_.cycle_period = 3000
wave3_pattern_.transition_type = animation.global('SINE_', 'SINE')
wave3_pattern_.brightness = 220
var plasma_wave3_ = animation.beacon_animation(engine)
plasma_wave3_.color = animation.global('wave3_pattern_', 'wave3_pattern')
plasma_wave3_.pos = 20
plasma_wave3_.beacon_size = 12
plasma_wave3_.slew_size = 6 # smooth
animation.global('plasma_wave3_').priority = 12
animation.global('plasma_wave3_').pos = animation.smooth(20, 50, 6000) # Different speed
var temp_smooth_315 = animation.smooth(engine)
temp_smooth_315.min_value = 20
temp_smooth_315.max_value = 50
temp_smooth_315.duration = 6000
animation.global('plasma_wave3_').pos = temp_smooth_315 # Different speed
# Add subtle intensity variation
animation.global('plasma_base_').opacity = animation.smooth(150, 255, 12000)
var temp_smooth_338 = animation.smooth(engine)
temp_smooth_338.min_value = 150
temp_smooth_338.max_value = 255
temp_smooth_338.duration = 12000
animation.global('plasma_base_').opacity = temp_smooth_338
# Start all animations
# Start all animations/sequences
if global.contains('sequence_plasma_base')

View File

@ -2,41 +2,41 @@
# Source: police_lights.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Police Lights - Red and blue alternating flashes
# # Emergency vehicle style lighting
#
# strip length 60
# #strip length 60
#
# # Define zones for left and right halves
# set half_length = 30
#
# # Left side red flashing
# animation left_red = pulse_position_animation(
# 0xFF0000, # Bright red
# 15, # center of left half
# 15, # half the strip
# 2 # sharp edges
# animation left_red = beacon_animation(
# color=0xFF0000, # Bright red
# pos=15, # center of left half
# beacon_size=15, # half the strip
# slew_size=2 # sharp edges
# )
# left_red.priority = 10
# left_red.opacity = square(0, 255, 400ms, 50) # 50% duty cycle
# left_red.opacity = square(min_value=0, max_value=255, duration=400ms, duty_cycle=50) # 50% duty cycle
#
# # Right side blue flashing (opposite phase)
# animation right_blue = pulse_position_animation(
# 0x0000FF, # Bright blue
# 45, # center of right half
# 15, # half the strip
# 2 # sharp edges
# animation right_blue = beacon_animation(
# color=0x0000FF, # Bright blue
# pos=45, # center of right half
# beacon_size=15, # half the strip
# slew_size=2 # sharp edges
# )
# right_blue.priority = 10
# right_blue.opacity = square(255, 0, 400ms, 50) # Opposite phase
# right_blue.opacity = square(min_value=255, max_value=0, duration=400ms, duty_cycle=50) # Opposite phase
#
# # Add white strobe overlay occasionally
# animation white_strobe = solid(0xFFFFFF)
# white_strobe.opacity = square(0, 255, 100ms, 5) # Quick bright flashes
# animation white_strobe = solid(color=0xFFFFFF)
# white_strobe.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=5) # Quick bright flashes
# white_strobe.priority = 20
#
# # Start all animations
@ -48,20 +48,47 @@ import animation
# Police Lights - Red and blue alternating flashes
# Emergency vehicle style lighting
var engine = animation.init_strip(60)
#strip length 60
# Define zones for left and right halves
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var half_length_ = 30
# Left side red flashing
var left_red_ = animation.pulse_position_animation(0xFFFF0000, 15, 15, 2)
var left_red_ = animation.beacon_animation(engine)
left_red_.color = 0xFFFF0000
left_red_.pos = 15
left_red_.beacon_size = 15
left_red_.slew_size = 2 # sharp edges
animation.global('left_red_').priority = 10
animation.global('left_red_').opacity = animation.square(0, 255, 400, 50) # 50% duty cycle
var temp_square_60 = animation.square(engine)
temp_square_60.min_value = 0
temp_square_60.max_value = 255
temp_square_60.duration = 400
temp_square_60.duty_cycle = 50
animation.global('left_red_').opacity = temp_square_60 # 50% duty cycle
# Right side blue flashing (opposite phase)
var right_blue_ = animation.pulse_position_animation(0xFF0000FF, 45, 15, 2)
var right_blue_ = animation.beacon_animation(engine)
right_blue_.color = 0xFF0000FF
right_blue_.pos = 45
right_blue_.beacon_size = 15
right_blue_.slew_size = 2 # sharp edges
animation.global('right_blue_').priority = 10
animation.global('right_blue_').opacity = animation.square(255, 0, 400, 50) # Opposite phase
var temp_square_124 = animation.square(engine)
temp_square_124.min_value = 255
temp_square_124.max_value = 0
temp_square_124.duration = 400
temp_square_124.duty_cycle = 50
animation.global('right_blue_').opacity = temp_square_124 # Opposite phase
# Add white strobe overlay occasionally
var white_strobe_ = animation.solid(0xFFFFFFFF)
animation.global('white_strobe_').opacity = animation.square(0, 255, 100, 5) # Quick bright flashes
var white_strobe_ = animation.solid(engine)
white_strobe_.color = 0xFFFFFFFF
var temp_square_161 = animation.square(engine)
temp_square_161.min_value = 0
temp_square_161.max_value = 255
temp_square_161.duration = 100
temp_square_161.duty_cycle = 5
animation.global('white_strobe_').opacity = temp_square_161 # Quick bright flashes
animation.global('white_strobe_').priority = 20
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: property_assignment_demo.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Property Assignment Demo
# # Shows how to set animation properties after creation
#
# strip length 60
# #strip length 60
#
# # Define colors
# color red_custom = 0xFF0000
@ -17,12 +17,12 @@
# color green_custom = 0x00FF00
#
# # Create animations
# animation left_pulse = pulse_position_animation(red_custom, 15, 15, 3)
# animation center_pulse = pulse_position_animation(blue_custom, 30, 15, 3)
# animation right_pulse = pulse_position_animation(green_custom, 45, 15, 3)
# animation left_pulse = beacon_animation(color=red_custom, pos=15, beacon_size=15, slew_size=3)
# animation center_pulse = beacon_animation(color=blue_custom, pos=30, beacon_size=15, slew_size=3)
# animation right_pulse = beacon_animation(color=green_custom, pos=45, beacon_size=15, slew_size=3)
#
# # Set different opacities
# left_pulse.opacity = 255 # Full brightness
# left_pulse.opacity = 255 # Full slew_size
# center_pulse.opacity = 200 # Slightly dimmed
# right_pulse.opacity = 150 # More dimmed
#
@ -54,17 +54,32 @@ import animation
# Property Assignment Demo
# Shows how to set animation properties after creation
var engine = animation.init_strip(60)
#strip length 60
# Define colors
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var red_custom_ = 0xFFFF0000
var blue_custom_ = 0xFF0000FF
var green_custom_ = 0xFF00FF00
# Create animations
var left_pulse_ = animation.pulse_position_animation(animation.global('red_custom_', 'red_custom'), 15, 15, 3)
var center_pulse_ = animation.pulse_position_animation(animation.global('blue_custom_', 'blue_custom'), 30, 15, 3)
var right_pulse_ = animation.pulse_position_animation(animation.global('green_custom_', 'green_custom'), 45, 15, 3)
var left_pulse_ = animation.beacon_animation(engine)
left_pulse_.color = animation.global('red_custom_', 'red_custom')
left_pulse_.pos = 15
left_pulse_.beacon_size = 15
left_pulse_.slew_size = 3
var center_pulse_ = animation.beacon_animation(engine)
center_pulse_.color = animation.global('blue_custom_', 'blue_custom')
center_pulse_.pos = 30
center_pulse_.beacon_size = 15
center_pulse_.slew_size = 3
var right_pulse_ = animation.beacon_animation(engine)
right_pulse_.color = animation.global('green_custom_', 'green_custom')
right_pulse_.pos = 45
right_pulse_.beacon_size = 15
right_pulse_.slew_size = 3
# Set different opacities
animation.global('left_pulse_').opacity = 255 # Full brightness
animation.global('left_pulse_').opacity = 255 # Full slew_size
animation.global('center_pulse_').opacity = 200 # Slightly dimmed
animation.global('right_pulse_').opacity = 150 # More dimmed
# Set priorities (higher numbers have priority)

View File

@ -2,37 +2,45 @@
# Source: rainbow_cycle.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Rainbow Cycle - Classic WLED effect
# # Smooth rainbow colors cycling across the strip
#
# strip length 60
# #strip length 60
#
# # Create smooth rainbow cycle animation
# animation rainbow_cycle = color_cycle_animation(
# [0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
# 5s # cycle period
# color rainbow_cycle = color_cycle(
# palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
# cycle_period=5s # cycle period
# )
# animation rainbow_animation = solid(color=rainbow_cycle)
#
# # Start the animation
# run rainbow_cycle
# run rainbow_animation
import animation
# Rainbow Cycle - Classic WLED effect
# Smooth rainbow colors cycling across the strip
var engine = animation.init_strip(60)
#strip length 60
# Create smooth rainbow cycle animation
var rainbow_cycle_ = animation.color_cycle_animation([0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF], 5000)
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var rainbow_cycle_ = animation.color_cycle(engine)
rainbow_cycle_.palette = [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF]
rainbow_cycle_.cycle_period = 5000 # cycle period
var rainbow_animation_ = animation.solid(engine)
rainbow_animation_.color = animation.global('rainbow_cycle_', 'rainbow_cycle')
# Start the animation
# Start all animations/sequences
if global.contains('sequence_rainbow_cycle')
var seq_manager = global.sequence_rainbow_cycle()
if global.contains('sequence_rainbow_animation')
var seq_manager = global.sequence_rainbow_animation()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('rainbow_cycle_'))
engine.add_animation(animation.global('rainbow_animation_'))
end
engine.start()

View File

@ -1,54 +0,0 @@
#!/usr/bin/env berry
# Test runner for compiled DSL examples
# Generated automatically by compile_dsl_examples.be
import os
import sys
# Add animation library path
sys.path().push("lib/libesp32/berry_animation")
# Import animation framework
import animation
def run_compiled_example(filename)
print(f"Running {filename}...")
try
var f = open(f"anim_examples/compiled/{filename}", "r")
var code = f.read()
f.close()
var compiled_func = compile(code)
if compiled_func != nil
compiled_func()
print(f" ✓ {filename} executed successfully")
return true
else
print(f" ✗ {filename} failed to compile")
return false
end
except .. as e, msg
print(f" ✗ {filename} execution failed: {msg}")
return false
end
end
def run_all_examples()
var files = os.listdir("compiled")
var success_count = 0
var total_count = 0
for file : files
if string.endswith(file, ".be")
total_count += 1
if run_compiled_example(file)
success_count += 1
end
end
end
print(f"\nTest Results: {success_count}/{total_count} examples ran successfully")
end
# Run all examples if script is executed directly
run_all_examples()

View File

@ -2,40 +2,41 @@
# Source: scanner_larson.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Scanner (Larson) - Knight Rider style scanner
# # Red dot bouncing back and forth
#
# strip length 60
# #strip length 60
#
# # Dark background
# color scanner_bg = 0x110000
# animation background = solid(scanner_bg)
# animation background = solid(color=scanner_bg)
#
# # Main scanner pulse that bounces
# animation scanner = pulse_position_animation(
# 0xFF0000, # Bright red
# 2, # initial position
# 3, # pulse width
# 2 # fade region
# animation scanner = beacon_animation(
# color=0xFF0000, # Bright red
# pos=2, # initial position
# beacon_size=3, # pulse width
# slew_size=2 # fade region
# )
# scanner.priority = 10
#
# # Bouncing position from left to right and back
# scanner.pos = triangle(2, 57, 2s)
# scanner.pos = triangle(min_value=2, max_value=57, duration=2s)
#
# # Add trailing glow effect
# animation scanner_trail = pulse_position_animation(
# 0x660000, # Dim red trail
# 2, # initial position
# 6, # wider trail
# 4 # more fade
# animation scanner_trail = beacon_animation(
# color=0x660000, # Dim red trail
# pos=2, # initial position
# beacon_size=6, # wider trail
# slew_size=4 # more fade
# )
# scanner_trail.priority = 5
# scanner_trail.pos = triangle(2, 57, 2s)
# set pos_test = triangle(min_value=2, max_value=57, duration=2s)
# scanner_trail.pos = pos_test
# scanner_trail.opacity = 128 # Half brightness
#
# # Start all animations
@ -47,19 +48,40 @@ import animation
# Scanner (Larson) - Knight Rider style scanner
# Red dot bouncing back and forth
var engine = animation.init_strip(60)
#strip length 60
# Dark background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var scanner_bg_ = 0xFF110000
var background_ = animation.solid(animation.global('scanner_bg_', 'scanner_bg'))
var background_ = animation.solid(engine)
background_.color = animation.global('scanner_bg_', 'scanner_bg')
# Main scanner pulse that bounces
var scanner_ = animation.pulse_position_animation(0xFFFF0000, 2, 3, 2)
var scanner_ = animation.beacon_animation(engine)
scanner_.color = 0xFFFF0000
scanner_.pos = 2
scanner_.beacon_size = 3
scanner_.slew_size = 2 # fade region
animation.global('scanner_').priority = 10
# Bouncing position from left to right and back
animation.global('scanner_').pos = animation.triangle(2, 57, 2000)
var temp_triangle_73 = animation.triangle(engine)
temp_triangle_73.min_value = 2
temp_triangle_73.max_value = 57
temp_triangle_73.duration = 2000
animation.global('scanner_').pos = temp_triangle_73
# Add trailing glow effect
var scanner_trail_ = animation.pulse_position_animation(0xFF660000, 2, 6, 4)
var scanner_trail_ = animation.beacon_animation(engine)
scanner_trail_.color = 0xFF660000
scanner_trail_.pos = 2
scanner_trail_.beacon_size = 6
scanner_trail_.slew_size = 4 # more fade
animation.global('scanner_trail_').priority = 5
animation.global('scanner_trail_').pos = animation.triangle(2, 57, 2000)
var temp_triangle_131 = animation.triangle(engine)
temp_triangle_131.min_value = 2
temp_triangle_131.max_value = 57
temp_triangle_131.duration = 2000
var pos_test_ = temp_triangle_131
animation.global('scanner_trail_').pos = animation.global('pos_test_', 'pos_test')
animation.global('scanner_trail_').opacity = 128 # Half brightness
# Start all animations
# Start all animations/sequences

View File

@ -2,14 +2,14 @@
# Source: simple_palette.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Simple Palette Example
# # Demonstrates basic palette usage in the DSL
#
# strip length 20
# #strip length 20
#
# # Define a simple rainbow palette
# palette rainbow = [
@ -21,7 +21,7 @@
# ]
#
# # Create an animation using the palette
# animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
# animation rainbow_cycle = rich_palette_animation(palette=rainbow, cycle_period=3s)
#
# # Simple sequence
# sequence demo {
@ -34,11 +34,16 @@ import animation
# Simple Palette Example
# Demonstrates basic palette usage in the DSL
var engine = animation.init_strip(20)
#strip length 20
# Define a simple rainbow palette
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var rainbow_ = bytes("00FF0000" "40FFA500" "80FFFF00" "C0008000" "FF0000FF")
# Create an animation using the palette
var rainbow_cycle_ = animation.rich_palette_animation(animation.global('rainbow_', 'rainbow'), 3000)
var rainbow_cycle_ = animation.rich_palette_animation(engine)
rainbow_cycle_.palette = animation.global('rainbow_', 'rainbow')
rainbow_cycle_.cycle_period = 3000
# Simple sequence
def sequence_demo()
var steps = []

View File

@ -2,14 +2,14 @@
# Source: sunrise_sunset.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Sunrise Sunset - Warm color transition
# # Gradual transition from night to day colors
#
# strip length 60
# #strip length 60
#
# # Define time-of-day color palette
# palette daylight_colors = [
@ -25,38 +25,38 @@
# ]
#
# # Main daylight cycle - very slow transition
# animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
# animation daylight_cycle = rich_palette_animation(palette=daylight_colors, cycle_period=60s)
#
# # Add sun position effect - bright spot that moves
# animation sun_position = pulse_position_animation(
# 0xFFFFAA, # Bright yellow sun
# 5, # initial position
# 8, # sun size
# 4 # soft glow
# animation sun_position = beacon_animation(
# color=0xFFFFAA, # Bright yellow sun
# pos=5, # initial position
# beacon_size=8, # sun size
# slew_size=4 # soft glow
# )
# sun_position.priority = 10
# sun_position.pos = smooth(5, 55, 30s) # Sun arc across sky
# sun_position.opacity = smooth(0, 255, 30s) # Fade in and out
# sun_position.pos = smooth(min_value=5, max_value=55, duration=30s) # Sun arc across sky
# sun_position.opacity = smooth(min_value=0, max_value=255, duration=30s) # Fade in and out
#
# # Add atmospheric glow around sun
# animation sun_glow = pulse_position_animation(
# 0xFFCC88, # Warm glow
# 5, # initial position
# 16, # larger glow
# 8 # very soft
# animation sun_glow = beacon_animation(
# color=0xFFCC88, # Warm glow
# pos=5, # initial position
# beacon_size=16, # larger glow
# slew_size=8 # very soft
# )
# sun_glow.priority = 5
# sun_glow.pos = smooth(5, 55, 30s) # Follow sun
# sun_glow.opacity = smooth(0, 150, 30s) # Dimmer glow
# sun_glow.pos = smooth(min_value=5, max_value=55, duration=30s) # Follow sun
# sun_glow.opacity = smooth(min_value=0, max_value=150, duration=30s) # Dimmer glow
#
# # Add twinkling stars during night phases
# animation stars = twinkle_animation(
# 0xFFFFFF, # White stars
# 6, # density (star count)
# 1s # twinkle speed (slow twinkle)
# color=0xFFFFFF, # White stars
# density=6, # density (star count)
# twinkle_speed=1s # twinkle speed (slow twinkle)
# )
# stars.priority = 15
# stars.opacity = smooth(255, 0, 30s) # Fade out during day
# stars.opacity = smooth(min_value=255, max_value=0, duration=30s) # Fade out during day
#
# # Start all animations
# run daylight_cycle
@ -68,25 +68,61 @@ import animation
# Sunrise Sunset - Warm color transition
# Gradual transition from night to day colors
var engine = animation.init_strip(60)
#strip length 60
# Define time-of-day color palette
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var daylight_colors_ = bytes("00000011" "20001133" "40FF4400" "60FFAA00" "80FFFF88" "A0FFAA44" "C0FF6600" "E0AA2200" "FF220011")
# Main daylight cycle - very slow transition
var daylight_cycle_ = animation.rich_palette_animation(animation.global('daylight_colors_', 'daylight_colors'), 60000)
var daylight_cycle_ = animation.rich_palette_animation(engine)
daylight_cycle_.palette = animation.global('daylight_colors_', 'daylight_colors')
daylight_cycle_.cycle_period = 60000
# Add sun position effect - bright spot that moves
var sun_position_ = animation.pulse_position_animation(0xFFFFFFAA, 5, 8, 4)
var sun_position_ = animation.beacon_animation(engine)
sun_position_.color = 0xFFFFFFAA
sun_position_.pos = 5
sun_position_.beacon_size = 8
sun_position_.slew_size = 4 # soft glow
animation.global('sun_position_').priority = 10
animation.global('sun_position_').pos = animation.smooth(5, 55, 30000) # Sun arc across sky
animation.global('sun_position_').opacity = animation.smooth(0, 255, 30000) # Fade in and out
var temp_smooth_150 = animation.smooth(engine)
temp_smooth_150.min_value = 5
temp_smooth_150.max_value = 55
temp_smooth_150.duration = 30000
animation.global('sun_position_').pos = temp_smooth_150 # Sun arc across sky
var temp_smooth_170 = animation.smooth(engine)
temp_smooth_170.min_value = 0
temp_smooth_170.max_value = 255
temp_smooth_170.duration = 30000
animation.global('sun_position_').opacity = temp_smooth_170 # Fade in and out
# Add atmospheric glow around sun
var sun_glow_ = animation.pulse_position_animation(0xFFFFCC88, 5, 16, 8)
var sun_glow_ = animation.beacon_animation(engine)
sun_glow_.color = 0xFFFFCC88
sun_glow_.pos = 5
sun_glow_.beacon_size = 16
sun_glow_.slew_size = 8 # very soft
animation.global('sun_glow_').priority = 5
animation.global('sun_glow_').pos = animation.smooth(5, 55, 30000) # Follow sun
animation.global('sun_glow_').opacity = animation.smooth(0, 150, 30000) # Dimmer glow
var temp_smooth_230 = animation.smooth(engine)
temp_smooth_230.min_value = 5
temp_smooth_230.max_value = 55
temp_smooth_230.duration = 30000
animation.global('sun_glow_').pos = temp_smooth_230 # Follow sun
var temp_smooth_250 = animation.smooth(engine)
temp_smooth_250.min_value = 0
temp_smooth_250.max_value = 150
temp_smooth_250.duration = 30000
animation.global('sun_glow_').opacity = temp_smooth_250 # Dimmer glow
# Add twinkling stars during night phases
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 6, 1000)
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFFFFFFF
stars_.density = 6
stars_.twinkle_speed = 1000 # twinkle speed (slow twinkle)
animation.global('stars_').priority = 15
animation.global('stars_').opacity = animation.smooth(255, 0, 30000) # Fade out during day
var temp_smooth_304 = animation.smooth(engine)
temp_smooth_304.min_value = 255
temp_smooth_304.max_value = 0
temp_smooth_304.duration = 30000
animation.global('stars_').opacity = temp_smooth_304 # Fade out during day
# Start all animations
# Start all animations/sequences
if global.contains('sequence_daylight_cycle')

View File

@ -1,46 +0,0 @@
#!/usr/bin/env berry
# Simple test runner for working DSL examples
import os
import sys
sys.path().push("lib/libesp32/berry_animation")
import animation
def test_compiled_file(filename)
print(f"Testing {filename}...")
try
var f = open(f"anim_examples/compiled/{filename}", "r")
var code = f.read()
f.close()
# Try to compile the Berry code
var compiled_func = compile(code)
if compiled_func != nil
print(f" ✓ {filename} compiles successfully")
return true
else
print(f" ✗ {filename} failed to compile")
return false
end
except .. as e, msg
print(f" ✗ {filename} test failed: {msg}")
return false
end
end
# Test all .be files in compiled directory
var files = os.listdir("compiled")
var success_count = 0
var total_count = 0
for file : files
import string
if string.endswith(file, ".be")
total_count += 1
if test_compiled_file(file)
success_count += 1
end
end
end
print(f"\nResults: {success_count}/{total_count} files compiled successfully")

View File

@ -2,32 +2,32 @@
# Source: twinkle_stars.anim
# Generated automatically
#
# This file was automatically generated by compile_all_dsl_examples.sh
# This file was automatically generated by compile_all_examples.sh
# Do not edit manually - changes will be overwritten
# Original DSL source:
# # Twinkle Stars - Random sparkling white stars
# # White sparkles on dark blue background
#
# strip length 60
# #strip length 60
#
# # Dark blue background
# color night_sky = 0x000033
# animation background = solid(night_sky)
# animation background = solid(color=night_sky)
#
# # White twinkling stars
# animation stars = twinkle_animation(
# 0xFFFFFF, # White stars
# 8, # density (number of stars)
# 500ms # twinkle speed (twinkle duration)
# color=0xFFFFFF, # White stars
# density=8, # density (number of stars)
# twinkle_speed=500ms # twinkle speed (twinkle duration)
# )
# stars.priority = 10
#
# # Add occasional bright flash
# animation bright_flash = twinkle_animation(
# 0xFFFFAA, # Bright yellow-white
# 2, # density (fewer bright flashes)
# 300ms # twinkle speed (quick flash)
# color=0xFFFFAA, # Bright yellow-white
# density=2, # density (fewer bright flashes)
# twinkle_speed=300ms # twinkle speed (quick flash)
# )
# bright_flash.priority = 15
#
@ -40,15 +40,25 @@ import animation
# Twinkle Stars - Random sparkling white stars
# White sparkles on dark blue background
var engine = animation.init_strip(60)
#strip length 60
# Dark blue background
# Auto-generated strip initialization (using Tasmota configuration)
var engine = animation.init_strip()
var night_sky_ = 0xFF000033
var background_ = animation.solid(animation.global('night_sky_', 'night_sky'))
var background_ = animation.solid(engine)
background_.color = animation.global('night_sky_', 'night_sky')
# White twinkling stars
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 8, 500)
var stars_ = animation.twinkle_animation(engine)
stars_.color = 0xFFFFFFFF
stars_.density = 8
stars_.twinkle_speed = 500 # twinkle speed (twinkle duration)
animation.global('stars_').priority = 10
# Add occasional bright flash
var bright_flash_ = animation.twinkle_animation(0xFFFFFFAA, 2, 300)
var bright_flash_ = animation.twinkle_animation(engine)
bright_flash_.color = 0xFFFFFFAA
bright_flash_.density = 2
bright_flash_.twinkle_speed = 300 # twinkle speed (quick flash)
animation.global('bright_flash_').priority = 15
# Start all animations
# Start all animations/sequences

View File

@ -1,7 +1,7 @@
# Disco Strobe - Fast colorful strobing
# Rapid color changes with strobe effects
strip length 60
#strip length 60
# Define disco color palette
palette disco_colors = [
@ -15,35 +15,35 @@ palette disco_colors = [
]
# Fast color cycling base
animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
# Add strobe effect
disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
# Add white flash overlay
animation white_flash = solid(0xFFFFFF)
white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
animation white_flash = solid(color=0xFFFFFF)
white_flash.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=10) # Quick white flashes
white_flash.priority = 20
# Add colored sparkles
pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
color sparkle_pattern = rich_palette(palette=disco_colors, cycle_period=500ms, transition_type=LINEAR, brightness=255)
animation disco_sparkles = twinkle_animation(
sparkle_pattern, # color source
12, # density (many sparkles)
80ms # twinkle speed (very quick)
color=sparkle_pattern, # color source
density=12, # density (many sparkles)
twinkle_speed=80ms # twinkle speed (very quick)
)
disco_sparkles.priority = 15
# Add moving pulse for extra effect
pattern pulse_pattern = rich_palette_color_provider(disco_colors, 800ms, linear, 255)
animation disco_pulse = pulse_position_animation(
pulse_pattern, # color source
4, # initial position
8, # pulse width
2 # sharp edges (slew size)
color pulse_pattern = rich_palette(palette=disco_colors, cycle_period=800ms, transition_type=LINEAR, brightness=255)
animation disco_pulse = beacon_animation(
color=pulse_pattern, # color source
pos=4, # initial position
beacon_size=8, # pulse width
slew_size=2 # sharp edges (slew size)
)
disco_pulse.priority = 10
disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
disco_pulse.pos = sawtooth(min_value=4, max_value=56, duration=2s) # Fast movement
# Start all animations
run disco_base

View File

@ -1,7 +1,7 @@
# Fire Flicker - Realistic fire simulation
# Warm colors with random flickering intensity
strip length 60
#strip length 60
# Define fire palette from black to yellow
palette fire_colors = [
@ -13,17 +13,17 @@ palette fire_colors = [
]
# Create base fire animation with palette
animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
# Add flickering effect with random intensity changes
fire_base.opacity = smooth(180, 255, 800ms)
fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
# Add subtle position variation for more realism
pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
color flicker_pattern = rich_palette(palette=fire_colors, cycle_period=2s, transition_type=LINEAR, brightness=255)
animation fire_flicker = twinkle_animation(
flicker_pattern, # color source
12, # density (number of flickers)
200ms # twinkle speed (flicker duration)
color=flicker_pattern, # color source
density=12, # density (number of flickers)
twinkle_speed=200ms # twinkle speed (flicker duration)
)
fire_flicker.priority = 10

View File

@ -1,38 +1,38 @@
# Heartbeat Pulse - Rhythmic double pulse
# Red pulsing like a heartbeat
strip length 60
#strip length 60
# Dark background
color heart_bg = 0x110000
animation background = solid(heart_bg)
animation background = solid(color=heart_bg)
# Define heartbeat timing - double pulse pattern
# Define heartbeat timing - double pulse animation
# First pulse (stronger)
animation heartbeat1 = solid(0xFF0000) # Bright red
heartbeat1.opacity = square(0, 255, 150ms, 20) # Quick strong pulse
animation heartbeat1 = solid(color=0xFF0000) # Bright red
heartbeat1.opacity = square(min_value=0, max_value=255, duration=150ms, duty_cycle=20) # Quick strong pulse
heartbeat1.priority = 10
# Second pulse (weaker, slightly delayed)
animation heartbeat2 = solid(0xCC0000) # Slightly dimmer red
animation heartbeat2 = solid(color=0xCC0000) # Slightly dimmer red
# Delay the second pulse by adjusting the square wave phase
heartbeat2.opacity = square(0, 180, 150ms, 15) # Weaker pulse
heartbeat2.opacity = square(min_value=0, max_value=180, duration=150ms, duty_cycle=15) # Weaker pulse
heartbeat2.priority = 8
# Add subtle glow effect
animation heart_glow = solid(0x660000) # Dim red glow
heart_glow.opacity = smooth(30, 100, 1s) # Gentle breathing glow
animation heart_glow = solid(color=0x660000) # Dim red glow
heart_glow.opacity = smooth(min_value=30, max_value=100, duration=1s) # Gentle breathing glow
heart_glow.priority = 5
# Add center pulse for emphasis
animation center_pulse = pulse_position_animation(
0xFFFFFF, # White center
30, # center of strip
4, # small center
2 # soft edges
animation center_pulse = beacon_animation(
color=0xFFFFFF, # White center
pos=30, # center of strip
beacon_size=4, # small center
slew_size=2 # soft edges
)
center_pulse.priority = 20
center_pulse.opacity = square(0, 200, 100ms, 10) # Quick white flash
center_pulse.opacity = square(min_value=0, max_value=200, duration=100ms, duty_cycle=10) # Quick white flash
# Start all animations
run background

View File

@ -1,7 +1,7 @@
# Lava Lamp - Slow flowing warm colors
# Organic movement like a lava lamp
strip length 60
#strip length 60
# Define lava colors (warm oranges and reds)
palette lava_colors = [
@ -13,45 +13,45 @@ palette lava_colors = [
]
# Base lava animation - very slow color changes
animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 180)
animation lava_base = rich_palette_animation(palette=lava_colors, cycle_period=15s, transition_type=SINE, brightness=180)
# Add slow-moving lava blobs
pattern blob1_pattern = rich_palette_color_provider(lava_colors, 12s, smooth, 255)
animation lava_blob1 = pulse_position_animation(
blob1_pattern, # color source
9, # initial position
18, # large blob
12 # very soft edges
color blob1_pattern = rich_palette(palette=lava_colors, cycle_period=12s, transition_type=SINE, brightness=255)
animation lava_blob1 = beacon_animation(
color=blob1_pattern, # color source
pos=9, # initial position
beacon_size=18, # large blob
slew_size=12 # very soft edges
)
lava_blob1.priority = 10
lava_blob1.pos = smooth(9, 51, 20s) # Very slow movement
lava_blob1.pos = smooth(min_value=9, max_value=51, duration=20s) # Very slow movement
pattern blob2_pattern = rich_palette_color_provider(lava_colors, 10s, smooth, 220)
animation lava_blob2 = pulse_position_animation(
blob2_pattern, # color source
46, # initial position
14, # medium blob
10 # soft edges
color blob2_pattern = rich_palette(palette=lava_colors, cycle_period=10s, transition_type=SINE, brightness=220)
animation lava_blob2 = beacon_animation(
color=blob2_pattern, # color source
pos=46, # initial position
beacon_size=14, # medium blob
slew_size=10 # soft edges
)
lava_blob2.priority = 8
lava_blob2.pos = smooth(46, 14, 25s) # Opposite direction, slower
lava_blob2.pos = smooth(min_value=46, max_value=14, duration=25s) # Opposite direction, slower
pattern blob3_pattern = rich_palette_color_provider(lava_colors, 8s, smooth, 200)
animation lava_blob3 = pulse_position_animation(
blob3_pattern, # color source
25, # initial position
10, # smaller blob
8 # soft edges
color blob3_pattern = rich_palette(palette=lava_colors, cycle_period=8s, transition_type=SINE, brightness=200)
animation lava_blob3 = beacon_animation(
color=blob3_pattern, # color source
pos=25, # initial position
beacon_size=10, # smaller blob
slew_size=8 # soft edges
)
lava_blob3.priority = 6
lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
lava_blob3.pos = smooth(min_value=25, max_value=35, duration=18s) # Small movement range
# Add subtle heat shimmer effect
pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
color shimmer_pattern = rich_palette(palette=lava_colors, cycle_period=6s, transition_type=SINE, brightness=255)
animation heat_shimmer = twinkle_animation(
shimmer_pattern, # color source
6, # density (shimmer points)
1.5s # twinkle speed (slow shimmer)
color=shimmer_pattern, # color source
density=6, # density (shimmer points)
twinkle_speed=1.5s # twinkle speed (slow shimmer)
)
heat_shimmer.priority = 15

View File

@ -1,7 +1,7 @@
# Lightning Storm - Random lightning flashes
# Dark stormy background with bright lightning
strip length 60
#strip length 60
# Dark stormy background with subtle purple/blue
palette storm_colors = [
@ -10,33 +10,33 @@ palette storm_colors = [
(255, 0x220033) # Slightly lighter purple
]
animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
animation storm_bg = rich_palette_animation(palette=storm_colors, cycle_period=12s, transition_type=SINE, brightness=100)
# Random lightning flashes - full strip
animation lightning_main = solid(0xFFFFFF) # Bright white
lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
animation lightning_main = solid(color=0xFFFFFF) # Bright white
lightning_main.opacity = square(min_value=0, max_value=255, duration=80ms, duty_cycle=3) # Quick bright flashes
lightning_main.priority = 20
# Secondary lightning - partial strip
animation lightning_partial = pulse_position_animation(
0xFFFFAA, # Slightly yellow white
30, # center position
20, # covers part of strip
5 # soft edges
animation lightning_partial = beacon_animation(
color=0xFFFFAA, # Slightly yellow white
pos=30, # center position
beacon_size=20, # covers part of strip
slew_size=5 # soft edges
)
lightning_partial.priority = 15
lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
lightning_partial.opacity = square(min_value=0, max_value=200, duration=120ms, duty_cycle=4) # Different timing
# Add blue afterglow
animation afterglow = solid(0x4444FF) # Blue glow
afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
animation afterglow = solid(color=0x4444FF) # Blue glow
afterglow.opacity = square(min_value=0, max_value=80, duration=200ms, duty_cycle=8) # Longer, dimmer glow
afterglow.priority = 10
# Distant thunder (dim flashes)
animation distant_flash = twinkle_animation(
0x666699, # Dim blue-white
4, # density (few flashes)
300ms # twinkle speed (medium duration)
color=0x666699, # Dim blue-white
density=4, # density (few flashes)
twinkle_speed=300ms # twinkle speed (medium duration)
)
distant_flash.priority = 5

View File

@ -1,11 +1,11 @@
# Matrix Rain - Digital rain effect
# Green cascading code like The Matrix
strip length 60
#strip length 60
# Dark background
color matrix_bg = 0x000000
animation background = solid(matrix_bg)
animation background = solid(color=matrix_bg)
# Define matrix green palette
palette matrix_greens = [
@ -17,35 +17,35 @@ palette matrix_greens = [
]
# Create multiple cascading streams
pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
color stream1_pattern = rich_palette(palette=matrix_greens, cycle_period=2s, transition_type=LINEAR, brightness=255)
animation stream1 = comet_animation(
stream1_pattern, # color source
15, # long tail
1.5s # speed
color=stream1_pattern, # color source
tail_length=15, # long tail
speed=1.5s # speed
)
stream1.priority = 10
pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
color stream2_pattern = rich_palette(palette=matrix_greens, cycle_period=1.8s, transition_type=LINEAR, brightness=200)
animation stream2 = comet_animation(
stream2_pattern, # color source
12, # medium tail
2.2s # different speed
color=stream2_pattern, # color source
tail_length=12, # medium tail
speed=2.2s # different speed
)
stream2.priority = 8
pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
color stream3_pattern = rich_palette(palette=matrix_greens, cycle_period=2.5s, transition_type=LINEAR, brightness=180)
animation stream3 = comet_animation(
stream3_pattern, # color source
10, # shorter tail
1.8s # another speed
color=stream3_pattern, # color source
tail_length=10, # shorter tail
speed=1.8s # another speed
)
stream3.priority = 6
# Add random bright flashes (like code highlights)
animation code_flash = twinkle_animation(
0x00FFAA, # Bright cyan-green
3, # density (few flashes)
150ms # twinkle speed (quick flash)
color=0x00FFAA, # Bright cyan-green
density=3, # density (few flashes)
twinkle_speed=150ms # twinkle speed (quick flash)
)
code_flash.priority = 20

View File

@ -1,54 +1,54 @@
# Meteor Shower - Multiple meteors with trails
# Fast moving bright objects with fading trails
strip length 60
#strip length 60
# Dark space background
color space_bg = 0x000011
animation background = solid(space_bg)
animation background = solid(color=space_bg)
# Multiple meteors with different speeds and colors
animation meteor1 = comet_animation(
0xFFFFFF, # Bright white
12, # long trail
1.5s # fast speed
color=0xFFFFFF, # Bright white
tail_length=12, # long trail
speed=1.5s # fast speed
)
meteor1.priority = 15
animation meteor2 = comet_animation(
0xFFAA00, # Orange
10, # medium trail
2s # medium speed
color=0xFFAA00, # Orange
tail_length=10, # medium trail
speed=2s # medium speed
)
meteor2.priority = 12
animation meteor3 = comet_animation(
0xAAAAFF, # Blue-white
8, # shorter trail
1.8s # fast speed
color=0xAAAAFF, # Blue-white
tail_length=8, # shorter trail
speed=1.8s # fast speed
)
meteor3.priority = 10
animation meteor4 = comet_animation(
0xFFAAAA, # Pink-white
14, # long trail
2.5s # slower speed
color=0xFFAAAA, # Pink-white
tail_length=14, # long trail
speed=2.5s # slower speed
)
meteor4.priority = 8
# Add distant stars
animation stars = twinkle_animation(
0xCCCCCC, # Dim white
12, # density (many stars)
2s # twinkle speed (slow twinkle)
color=0xCCCCCC, # Dim white
density=12, # density (many stars)
twinkle_speed=2s # twinkle speed (slow twinkle)
)
stars.priority = 5
# Add occasional bright flash (meteor explosion)
animation meteor_flash = twinkle_animation(
0xFFFFFF, # Bright white
1, # density (single flash)
100ms # twinkle speed (very quick)
color=0xFFFFFF, # Bright white
density=1, # density (single flash)
twinkle_speed=100ms # twinkle speed (very quick)
)
meteor_flash.priority = 25

View File

@ -1,7 +1,7 @@
# Neon Glow - Electric neon tube effect
# Bright saturated colors with flickering
strip length 60
#strip length 60
# Define neon colors
palette neon_colors = [
@ -12,47 +12,47 @@ palette neon_colors = [
]
# Main neon glow with color cycling
animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
animation neon_main = rich_palette_animation(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
# Add electrical flickering
neon_main.opacity = smooth(220, 255, 200ms)
neon_main.opacity = smooth(min_value=220, max_value=255, duration=200ms)
# Add occasional electrical surge
animation neon_surge = solid(0xFFFFFF) # White surge
neon_surge.opacity = square(0, 255, 50ms, 2) # Quick bright surges
animation neon_surge = solid(color=0xFFFFFF) # White surge
neon_surge.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=2) # Quick bright surges
neon_surge.priority = 20
# Add neon tube segments with gaps
pattern segment_pattern = rich_palette_color_provider(neon_colors, 4s, linear, 255)
animation segment1 = pulse_position_animation(
segment_pattern, # color source
6, # position
12, # segment length
1 # sharp edges
color segment_pattern = rich_palette(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
animation segment1 = beacon_animation(
color=segment_pattern, # color source
pos=6, # position
beacon_size=12, # segment length
slew_size=1 # sharp edges
)
segment1.priority = 10
animation segment2 = pulse_position_animation(
segment_pattern, # color source
24, # position
12, # segment length
1 # sharp edges
animation segment2 = beacon_animation(
color=segment_pattern, # color source
pos=24, # position
beacon_size=12, # segment length
slew_size=1 # sharp edges
)
segment2.priority = 10
animation segment3 = pulse_position_animation(
segment_pattern, # color source
42, # position
12, # segment length
1 # sharp edges
animation segment3 = beacon_animation(
color=segment_pattern, # color source
pos=42, # position
beacon_size=12, # segment length
slew_size=1 # sharp edges
)
segment3.priority = 10
# Add electrical arcing between segments
animation arc_sparkles = twinkle_animation(
0xAAAAFF, # Electric blue
4, # density (few arcs)
100ms # twinkle speed (quick arcs)
color=0xAAAAFF, # Electric blue
density=4, # density (few arcs)
twinkle_speed=100ms # twinkle speed (quick arcs)
)
arc_sparkles.priority = 15

View File

@ -1,7 +1,7 @@
# Ocean Waves - Blue-green wave simulation
# Flowing water colors with wave motion
strip length 60
#strip length 60
# Define ocean color palette
palette ocean_colors = [
@ -13,34 +13,34 @@ palette ocean_colors = [
]
# Base ocean animation with slow color cycling
animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 200)
animation ocean_base = rich_palette_animation(palette=ocean_colors, cycle_period=8s, transition_type=SINE, brightness=200)
# Add wave motion with moving pulses
pattern wave1_pattern = rich_palette_color_provider(ocean_colors, 6s, smooth, 255)
animation wave1 = pulse_position_animation(
wave1_pattern, # color source
0, # initial position
12, # wave width
6 # soft edges
color wave1_pattern = rich_palette(palette=ocean_colors, cycle_period=6s, transition_type=SINE, brightness=255)
animation wave1 = beacon_animation(
color=wave1_pattern, # color source
pos=0, # initial position
beacon_size=12, # wave width
slew_size=6 # soft edges
)
wave1.priority = 10
wave1.pos = sawtooth(0, 48, 5s) # 60-12 = 48
wave1.pos = sawtooth(min_value=0, max_value=48, duration=5s) # 60-12 = 48
pattern wave2_pattern = rich_palette_color_provider(ocean_colors, 4s, smooth, 180)
animation wave2 = pulse_position_animation(
wave2_pattern, # color source
52, # initial position
8, # smaller wave
4 # soft edges
color wave2_pattern = rich_palette(palette=ocean_colors, cycle_period=4s, transition_type=SINE, brightness=180)
animation wave2 = beacon_animation(
color=wave2_pattern, # color source
pos=52, # initial position
beacon_size=8, # smaller wave
slew_size=4 # soft edges
)
wave2.priority = 8
wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
wave2.pos = sawtooth(min_value=52, max_value=8, duration=7s) # Opposite direction
# Add foam sparkles
animation foam = twinkle_animation(
0xFFFFFF, # White foam
6, # density (sparkle count)
300ms # twinkle speed (quick sparkles)
color=0xFFFFFF, # White foam
density=6, # density (sparkle count)
twinkle_speed=300ms # twinkle speed (quick sparkles)
)
foam.priority = 15

View File

@ -1,7 +1,7 @@
# Palette Demo - Shows how to use custom palettes in DSL
# This demonstrates the new palette syntax
strip length 30
#strip length 30
# Define a fire palette
palette fire_colors = [
@ -22,9 +22,9 @@ palette ocean_colors = [
]
# Create animations using the palettes
animation fire_anim = rich_palette_animation(fire_colors, 5s)
animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5s)
animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
# Sequence to show both palettes
sequence palette_demo {

View File

@ -1,7 +1,7 @@
# Palette Showcase - Demonstrates all palette features
# This example shows the full range of palette capabilities
strip length 60
#strip length 60
# Example 1: Fire palette with hex colors
palette fire_gradient = [
@ -44,13 +44,13 @@ palette sunset_sky = [
]
# Create animations using each palette
animation fire_effect = rich_palette_animation(fire_gradient, 3s)
animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
animation aurora_lights = rich_palette_animation(palette=aurora_borealis, cycle_period=12s, transition_type=SINE, brightness=180)
animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
animation sunset_glow = rich_palette_animation(palette=sunset_sky, cycle_period=6s, transition_type=SINE, brightness=220)
# Sequence to showcase all palettes
sequence palette_showcase {

View File

@ -1,57 +0,0 @@
# Unified Pattern-Animation Demo
# This DSL example demonstrates the new unified architecture where Animation extends Pattern
strip length 30
# UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
# No more artificial distinction between patterns and animations
animation solid_red = solid(red) # Animation: solid red (infinite duration)
animation solid_blue = solid(blue) # Animation: solid blue (infinite duration)
animation solid_green = solid(green) # Animation: solid green (infinite duration)
# COMPOSITION: Animations can use other animations as base
animation pulsing_red = pulse(solid_red, 0%, 100%, 2s) # Animation using animation
animation pulsing_blue = pulse(solid_blue, 50%, 100%, 1s) # Animation using animation
animation pulsing_green = pulse(solid_green, 20%, 80%, 3s) # Animation using animation
# Set priorities (all animations inherit from Pattern)
solid_red.priority = 0 # Base animation priority
pulsing_red.priority = 10 # Higher priority
pulsing_blue.priority = 20 # Even higher priority
pulsing_green.priority = 5 # Medium priority
# Set opacity (all animations inherit from Pattern)
solid_red.opacity = 255 # Full opacity
pulsing_red.opacity = 200 # Slightly dimmed
pulsing_blue.opacity = 150 # More dimmed
# RECURSIVE COMPOSITION: Animations can use other animations!
# This creates infinitely composable effects
animation complex_pulse = pulse(pulsing_red, 30%, 70%, 4s) # Pulse a pulsing animation!
# Create a sequence that demonstrates the unified architecture
sequence unified_demo {
# All animations can be used directly in sequences
play solid_red for 2s # Use solid animation
wait 500ms
# Composed animations work seamlessly
play pulsing_red for 3s # Use pulse animation
wait 500ms
play pulsing_blue for 2s # Use another pulse animation
wait 500ms
# Show recursive composition - animation using animation
play complex_pulse for 4s # Nested animation effect
wait 500ms
# Show that all animations support the same properties
repeat 2 times:
play solid_green for 1s # Animation with priority/opacity
play pulsing_green for 2s # Animation with priority/opacity
wait 500ms
}
# Run the demonstration
run unified_demo

View File

@ -1,7 +1,7 @@
# Plasma Wave - Smooth flowing plasma colors
# Continuous color waves like plasma display
strip length 60
#strip length 60
# Define plasma color palette with smooth transitions
palette plasma_colors = [
@ -14,41 +14,41 @@ palette plasma_colors = [
]
# Base plasma animation with medium speed
animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 200)
animation plasma_base = rich_palette_animation(palette=plasma_colors, cycle_period=6s, transition_type=SINE, brightness=200)
# Add multiple wave layers for complexity
pattern wave1_pattern = rich_palette_color_provider(plasma_colors, 4s, smooth, 255)
animation plasma_wave1 = pulse_position_animation(
wave1_pattern, # color source
0, # initial position
20, # wide wave
10 # very smooth
color wave1_pattern = rich_palette(palette=plasma_colors, cycle_period=4s, transition_type=SINE, brightness=255)
animation plasma_wave1 = beacon_animation(
color=wave1_pattern, # color source
pos=0, # initial position
beacon_size=20, # wide wave
slew_size=10 # very smooth
)
plasma_wave1.priority = 10
plasma_wave1.pos = smooth(0, 40, 8s)
plasma_wave1.pos = smooth(min_value=0, max_value=40, duration=8s)
pattern wave2_pattern = rich_palette_color_provider(plasma_colors, 5s, smooth, 180)
animation plasma_wave2 = pulse_position_animation(
wave2_pattern, # color source
45, # initial position
15, # medium wave
8 # smooth
color wave2_pattern = rich_palette(palette=plasma_colors, cycle_period=5s, transition_type=SINE, brightness=180)
animation plasma_wave2 = beacon_animation(
color=wave2_pattern, # color source
pos=45, # initial position
beacon_size=15, # medium wave
slew_size=8 # smooth
)
plasma_wave2.priority = 8
plasma_wave2.pos = smooth(45, 15, 10s) # Opposite direction
plasma_wave2.pos = smooth(min_value=45, max_value=15, duration=10s) # Opposite direction
pattern wave3_pattern = rich_palette_color_provider(plasma_colors, 3s, smooth, 220)
animation plasma_wave3 = pulse_position_animation(
wave3_pattern, # color source
20, # initial position
12, # smaller wave
6 # smooth
color wave3_pattern = rich_palette(palette=plasma_colors, cycle_period=3s, transition_type=SINE, brightness=220)
animation plasma_wave3 = beacon_animation(
color=wave3_pattern, # color source
pos=20, # initial position
beacon_size=12, # smaller wave
slew_size=6 # smooth
)
plasma_wave3.priority = 12
plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
plasma_wave3.pos = smooth(min_value=20, max_value=50, duration=6s) # Different speed
# Add subtle intensity variation
plasma_base.opacity = smooth(150, 255, 12s)
plasma_base.opacity = smooth(min_value=150, max_value=255, duration=12s)
# Start all animations
run plasma_base

View File

@ -1,34 +1,34 @@
# Police Lights - Red and blue alternating flashes
# Emergency vehicle style lighting
strip length 60
#strip length 60
# Define zones for left and right halves
set half_length = 30
# Left side red flashing
animation left_red = pulse_position_animation(
0xFF0000, # Bright red
15, # center of left half
15, # half the strip
2 # sharp edges
animation left_red = beacon_animation(
color=0xFF0000, # Bright red
pos=15, # center of left half
beacon_size=15, # half the strip
slew_size=2 # sharp edges
)
left_red.priority = 10
left_red.opacity = square(0, 255, 400ms, 50) # 50% duty cycle
left_red.opacity = square(min_value=0, max_value=255, duration=400ms, duty_cycle=50) # 50% duty cycle
# Right side blue flashing (opposite phase)
animation right_blue = pulse_position_animation(
0x0000FF, # Bright blue
45, # center of right half
15, # half the strip
2 # sharp edges
animation right_blue = beacon_animation(
color=0x0000FF, # Bright blue
pos=45, # center of right half
beacon_size=15, # half the strip
slew_size=2 # sharp edges
)
right_blue.priority = 10
right_blue.opacity = square(255, 0, 400ms, 50) # Opposite phase
right_blue.opacity = square(min_value=255, max_value=0, duration=400ms, duty_cycle=50) # Opposite phase
# Add white strobe overlay occasionally
animation white_strobe = solid(0xFFFFFF)
white_strobe.opacity = square(0, 255, 100ms, 5) # Quick bright flashes
animation white_strobe = solid(color=0xFFFFFF)
white_strobe.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=5) # Quick bright flashes
white_strobe.priority = 20
# Start all animations

View File

@ -1,7 +1,7 @@
# Property Assignment Demo
# Shows how to set animation properties after creation
strip length 60
#strip length 60
# Define colors
color red_custom = 0xFF0000
@ -9,12 +9,12 @@ color blue_custom = 0x0000FF
color green_custom = 0x00FF00
# Create animations
animation left_pulse = pulse_position_animation(red_custom, 15, 15, 3)
animation center_pulse = pulse_position_animation(blue_custom, 30, 15, 3)
animation right_pulse = pulse_position_animation(green_custom, 45, 15, 3)
animation left_pulse = beacon_animation(color=red_custom, pos=15, beacon_size=15, slew_size=3)
animation center_pulse = beacon_animation(color=blue_custom, pos=30, beacon_size=15, slew_size=3)
animation right_pulse = beacon_animation(color=green_custom, pos=45, beacon_size=15, slew_size=3)
# Set different opacities
left_pulse.opacity = 255 # Full brightness
left_pulse.opacity = 255 # Full slew_size
center_pulse.opacity = 200 # Slightly dimmed
right_pulse.opacity = 150 # More dimmed

View File

@ -1,13 +1,14 @@
# Rainbow Cycle - Classic WLED effect
# Smooth rainbow colors cycling across the strip
strip length 60
#strip length 60
# Create smooth rainbow cycle animation
animation rainbow_cycle = color_cycle_animation(
[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
5s # cycle period
color rainbow_cycle = color_cycle(
palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
cycle_period=5s # cycle period
)
animation rainbow_animation = solid(color=rainbow_cycle)
# Start the animation
run rainbow_cycle
run rainbow_animation

View File

@ -1,33 +1,34 @@
# Scanner (Larson) - Knight Rider style scanner
# Red dot bouncing back and forth
strip length 60
#strip length 60
# Dark background
color scanner_bg = 0x110000
animation background = solid(scanner_bg)
animation background = solid(color=scanner_bg)
# Main scanner pulse that bounces
animation scanner = pulse_position_animation(
0xFF0000, # Bright red
2, # initial position
3, # pulse width
2 # fade region
animation scanner = beacon_animation(
color=0xFF0000, # Bright red
pos=2, # initial position
beacon_size=3, # pulse width
slew_size=2 # fade region
)
scanner.priority = 10
# Bouncing position from left to right and back
scanner.pos = triangle(2, 57, 2s)
scanner.pos = triangle(min_value=2, max_value=57, duration=2s)
# Add trailing glow effect
animation scanner_trail = pulse_position_animation(
0x660000, # Dim red trail
2, # initial position
6, # wider trail
4 # more fade
animation scanner_trail = beacon_animation(
color=0x660000, # Dim red trail
pos=2, # initial position
beacon_size=6, # wider trail
slew_size=4 # more fade
)
scanner_trail.priority = 5
scanner_trail.pos = triangle(2, 57, 2s)
set pos_test = triangle(min_value=2, max_value=57, duration=2s)
scanner_trail.pos = pos_test
scanner_trail.opacity = 128 # Half brightness
# Start all animations

View File

@ -1,7 +1,7 @@
# Simple Palette Example
# Demonstrates basic palette usage in the DSL
strip length 20
#strip length 20
# Define a simple rainbow palette
palette rainbow = [
@ -13,7 +13,7 @@ palette rainbow = [
]
# Create an animation using the palette
animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
animation rainbow_cycle = rich_palette_animation(palette=rainbow, cycle_period=3s)
# Simple sequence
sequence demo {

View File

@ -1,7 +1,7 @@
# Sunrise Sunset - Warm color transition
# Gradual transition from night to day colors
strip length 60
#strip length 60
# Define time-of-day color palette
palette daylight_colors = [
@ -17,38 +17,38 @@ palette daylight_colors = [
]
# Main daylight cycle - very slow transition
animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
animation daylight_cycle = rich_palette_animation(palette=daylight_colors, cycle_period=60s)
# Add sun position effect - bright spot that moves
animation sun_position = pulse_position_animation(
0xFFFFAA, # Bright yellow sun
5, # initial position
8, # sun size
4 # soft glow
animation sun_position = beacon_animation(
color=0xFFFFAA, # Bright yellow sun
pos=5, # initial position
beacon_size=8, # sun size
slew_size=4 # soft glow
)
sun_position.priority = 10
sun_position.pos = smooth(5, 55, 30s) # Sun arc across sky
sun_position.opacity = smooth(0, 255, 30s) # Fade in and out
sun_position.pos = smooth(min_value=5, max_value=55, duration=30s) # Sun arc across sky
sun_position.opacity = smooth(min_value=0, max_value=255, duration=30s) # Fade in and out
# Add atmospheric glow around sun
animation sun_glow = pulse_position_animation(
0xFFCC88, # Warm glow
5, # initial position
16, # larger glow
8 # very soft
animation sun_glow = beacon_animation(
color=0xFFCC88, # Warm glow
pos=5, # initial position
beacon_size=16, # larger glow
slew_size=8 # very soft
)
sun_glow.priority = 5
sun_glow.pos = smooth(5, 55, 30s) # Follow sun
sun_glow.opacity = smooth(0, 150, 30s) # Dimmer glow
sun_glow.pos = smooth(min_value=5, max_value=55, duration=30s) # Follow sun
sun_glow.opacity = smooth(min_value=0, max_value=150, duration=30s) # Dimmer glow
# Add twinkling stars during night phases
animation stars = twinkle_animation(
0xFFFFFF, # White stars
6, # density (star count)
1s # twinkle speed (slow twinkle)
color=0xFFFFFF, # White stars
density=6, # density (star count)
twinkle_speed=1s # twinkle speed (slow twinkle)
)
stars.priority = 15
stars.opacity = smooth(255, 0, 30s) # Fade out during day
stars.opacity = smooth(min_value=255, max_value=0, duration=30s) # Fade out during day
# Start all animations
run daylight_cycle

View File

@ -1,25 +1,25 @@
# Twinkle Stars - Random sparkling white stars
# White sparkles on dark blue background
strip length 60
#strip length 60
# Dark blue background
color night_sky = 0x000033
animation background = solid(night_sky)
animation background = solid(color=night_sky)
# White twinkling stars
animation stars = twinkle_animation(
0xFFFFFF, # White stars
8, # density (number of stars)
500ms # twinkle speed (twinkle duration)
color=0xFFFFFF, # White stars
density=8, # density (number of stars)
twinkle_speed=500ms # twinkle speed (twinkle duration)
)
stars.priority = 10
# Add occasional bright flash
animation bright_flash = twinkle_animation(
0xFFFFAA, # Bright yellow-white
2, # density (fewer bright flashes)
300ms # twinkle speed (quick flash)
color=0xFFFFAA, # Bright yellow-white
density=2, # density (fewer bright flashes)
twinkle_speed=300ms # twinkle speed (quick flash)
)
bright_flash.priority = 15

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,586 @@
# 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
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
# Use virtual parameter access - automatically resolves ValueProviders
var param1 = self.my_param1
var param2 = self.my_param2
# 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)
- **`"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)
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
# 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 resolved values in rendering logic
for i: position..(position + size - 1)
if i >= 0 && i < frame.width
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)
# 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 < frame.width
frame.set_pixel_color(i, current_color)
end
end
return true
end
```
## 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)
if !self.is_running || frame == nil
return false
end
# Get frame dimensions
var width = frame.width
var height = frame.height # Usually 1 for LED strips
# 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
for i: 0..(width-1)
var pixel_color = calculate_pixel_color(i, time_ms)
frame.set_pixel_color(i, pixel_color)
end
# Apply opacity if not full
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: PulsePositionAnimation
Here's a complete example showing all concepts:
```berry
#@ solidify:PulsePositionAnimation,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)
if frame == nil
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
var pixel_size = frame.width
# 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"PulsePositionAnimation(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': PulsePositionAnimation}
```
## 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.animation_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)
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.animation_engine(strip)
var anim = animation.my_animation(engine)
# Set parameters
anim.color = 0xFFFF0000
anim.pos = 5
anim.beacon_size = 3
engine.add_animation(anim)
engine.start()
# Let it run for a few seconds
tasmota.delay(3000)
engine.stop()
print("Integration test completed")
```
## Best Practices
### Performance
- **Minimize calculations** in render() method
- **Cache resolved values** when possible
- **Use integer math** instead of floating point
- **Avoid memory allocation** in render loops
### Memory Management
- **Reuse objects** when possible
- **Clear references** to large objects when done
- **Use static variables** for constants
### Code Organization
- **Group related parameters** together
- **Use descriptive variable names**
- **Comment complex algorithms**
- **Follow Berry naming conventions**
### Error Handling
- **Validate parameters** in constructor
- **Handle edge cases** gracefully
- **Return false** from render() on errors
- **Use meaningful error messages**
## Publishing Your Animation Class
Once you've created a new animation class:
1. **Add it to the animation module** by importing it in `animation.be`
2. **Create a factory function** following the engine-first pattern
3. **Add DSL support** by ensuring the transpiler recognizes your factory function
4. **Document parameters** in the class hierarchy documentation
5. **Test with DSL** to ensure users can access your animation declaratively
**Remember**: Users should primarily interact with animations through the DSL. The programmatic API is mainly for framework development and advanced integrations.
This guide provides everything needed to create professional-quality animation classes that integrate seamlessly with the Berry Animation Framework's parameter system and rendering pipeline.

View File

@ -1,750 +0,0 @@
# API Reference
Complete reference for the Tasmota Berry Animation Framework API.
## Core Classes
### AnimationEngine
The central controller for all animations.
```berry
var engine = animation.create_engine(strip)
```
#### Methods
**`add_animation(animation)`**
- Adds an animation to the engine
- Auto-starts the animation if engine is running
- Returns: `self` (for method chaining)
**`remove_animation(animation)`**
- Removes an animation from the engine
- Returns: `self`
**`clear()`**
- Removes all animations
- Returns: `self`
**`start()`**
- Starts the engine and all animations
- Integrates with Tasmota's `fast_loop`
- Returns: `self`
**`stop()`**
- Stops the engine and all animations
- Returns: `self`
**`size()`**
- Returns: Number of active animations
**`is_active()`**
- Returns: `true` if engine is running
#### Example
```berry
var strip = Leds(30)
var engine = animation.create_engine(strip)
var pulse = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
engine.add_animation(pulse).start()
```
### Pattern (Base Class)
Base class for all visual elements.
#### Properties
- **`priority`** (int) - Rendering priority (higher = on top)
- **`opacity`** (int) - Opacity 0-255 for blending
- **`name`** (string) - Pattern identification
- **`is_running`** (bool) - Whether pattern is active
#### Methods
**`start()`** / **`stop()`**
- Control pattern lifecycle
- Returns: `self`
**`set_priority(priority)`**
- Set rendering priority
- Returns: `self`
**`set_opacity(opacity)`**
- Set opacity (0-255)
- Returns: `self`
### Animation (Extends Pattern)
Adds temporal behavior to patterns.
#### Additional Properties
- **`duration`** (int) - Animation duration in ms (0 = infinite)
- **`loop`** (bool) - Whether to loop when complete
- **`start_time`** (int) - When animation started
- **`current_time`** (int) - Current animation time
#### Additional Methods
**`set_duration(duration_ms)`**
- Set animation duration
- Returns: `self`
**`set_loop(loop)`**
- Enable/disable looping
- Returns: `self`
**`get_progress()`**
- Returns: Animation progress (0-255)
## Animation Functions
### Basic Animations
**`animation.solid(color, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates solid color animation
- **color**: ARGB color value (0xAARRGGBB) or ValueProvider instance
- Returns: `PatternAnimation` instance
```berry
var red = animation.solid(0xFFFF0000)
var blue = animation.solid(0xFF0000FF, 10, 5000, true, 200, "blue_anim")
var dynamic = animation.solid(animation.smooth(0xFF000000, 0xFFFFFFFF, 3000))
```
**`animation.pulse(pattern, period_ms, min_brightness=0, max_brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates pulsing animation
- **pattern**: Base pattern to pulse
- **period_ms**: Pulse period in milliseconds
- **min_brightness**: Minimum brightness (0-255)
- **max_brightness**: Maximum brightness (0-255)
- Returns: `PulseAnimation` instance
```berry
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
```
**`animation.breathe(color, period_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates smooth breathing effect
- **color**: ARGB color value or ValueProvider instance
- **period_ms**: Breathing period in milliseconds
- Returns: `BreatheAnimation` instance
```berry
var breathe_blue = animation.breathe(0xFF0000FF, 4000)
var dynamic_breathe = animation.breathe(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00], 2000), 4000)
```
### Palette-Based Animations
**`animation.rich_palette_animation(palette, period_ms, transition_type=1, brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates palette-based color cycling animation
- **palette**: Palette in VRGB bytes format or palette name
- **period_ms**: Cycle period in milliseconds
- **transition_type**: 0=linear, 1=smooth (sine)
- **brightness**: Overall brightness (0-255)
- Returns: `FilledAnimation` instance
```berry
var rainbow = animation.rich_palette_animation(animation.PALETTE_RAINBOW, 5000, 1, 255)
```
### Position-Based Animations
**`animation.pulse_position_animation(color, pos, pulse_size, slew_size=0, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates pulse at specific position
- **color**: ARGB color value or ValueProvider instance
- **pos**: Pixel position (0-based) or ValueProvider instance
- **pulse_size**: Width of pulse in pixels or ValueProvider instance
- **slew_size**: Fade region size in pixels or ValueProvider instance
- Returns: `PulsePositionAnimation` instance
```berry
var center_pulse = animation.pulse_position_animation(0xFFFFFFFF, 15, 3, 2)
var moving_pulse = animation.pulse_position_animation(0xFFFF0000, animation.smooth(0, 29, 3000), 3, 2)
```
**`animation.comet_animation(color, tail_length, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates moving comet effect
- **color**: ARGB color value or ValueProvider instance
- **tail_length**: Length of comet tail in pixels
- **speed_ms**: Movement speed in milliseconds per pixel
- Returns: `CometAnimation` instance
```berry
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
var rainbow_comet = animation.comet_animation(animation.rich_palette_color_provider(animation.PALETTE_RAINBOW, 3000), 8, 100)
```
**`animation.twinkle_animation(color, density, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates twinkling stars effect
- **color**: ARGB color value or ValueProvider instance
- **density**: Number of twinkling pixels
- **speed_ms**: Twinkle speed in milliseconds
- Returns: `TwinkleAnimation` instance
```berry
var stars = animation.twinkle_animation(0xFFFFFFFF, 5, 500)
var color_changing_stars = animation.twinkle_animation(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00, 0xFF0000FF], 4000), 5, 500)
```
### Fire and Natural Effects
**`animation.fire_animation(color=nil, intensity=200, speed_ms=100, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates realistic fire simulation
- **color**: ARGB color value, ValueProvider instance, or nil for default fire palette
- **intensity**: Fire intensity (0-255)
- **speed_ms**: Animation speed in milliseconds
- Returns: `FireAnimation` instance
```berry
var fire = animation.fire_animation(nil, 180, 150) # Default fire palette
var blue_fire = animation.fire_animation(0xFF0066FF, 180, 150) # Blue fire
```
### Advanced Pattern Animations
**`animation.noise_rainbow(scale, speed, strip_length, priority)`**
- Creates rainbow noise pattern with fractal complexity
- **scale**: Noise frequency/detail (0-255, higher = more detail)
- **speed**: Animation speed (0-255, 0 = static)
- **strip_length**: LED strip length
- **priority**: Rendering priority
- Returns: `NoiseAnimation` instance
**`animation.noise_single_color(color, scale, speed, strip_length, priority)`**
- Creates single-color noise pattern
- **color**: ARGB color value or ValueProvider instance
- Returns: `NoiseAnimation` instance
**`animation.noise_fractal(color, scale, speed, octaves, strip_length, priority)`**
- Creates multi-octave fractal noise
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
- **octaves**: Number of noise octaves (1-4)
- Returns: `NoiseAnimation` instance
```berry
var rainbow_noise = animation.noise_rainbow(60, 40, 30, 10)
var blue_noise = animation.noise_single_color(0xFF0066FF, 120, 60, 30, 10)
var fractal = animation.noise_fractal(nil, 40, 50, 3, 30, 10)
```
**`animation.plasma_rainbow(time_speed, strip_length, priority)`**
- Creates rainbow plasma effect using sine wave interference
- **time_speed**: Animation speed (0-255)
- Returns: `PlasmaAnimation` instance
**`animation.plasma_single_color(color, time_speed, strip_length, priority)`**
- Creates single-color plasma effect
- **color**: ARGB color value or ValueProvider instance
- Returns: `PlasmaAnimation` instance
```berry
var plasma = animation.plasma_rainbow(80, 30, 10)
var purple_plasma = animation.plasma_single_color(0xFF8800FF, 60, 30, 10)
```
**`animation.sparkle_white(density, fade_speed, strip_length, priority)`**
- Creates white twinkling sparkles
- **density**: Sparkle creation probability (0-255)
- **fade_speed**: Fade-out speed (0-255)
- Returns: `SparkleAnimation` instance
**`animation.sparkle_colored(color, density, fade_speed, strip_length, priority)`**
- Creates colored sparkles
- **color**: ARGB color value or ValueProvider instance
- Returns: `SparkleAnimation` instance
**`animation.sparkle_rainbow(density, fade_speed, strip_length, priority)`**
- Creates rainbow sparkles
- Returns: `SparkleAnimation` instance
```berry
var white_sparkles = animation.sparkle_white(80, 60, 30, 10)
var red_sparkles = animation.sparkle_colored(0xFFFF0000, 100, 50, 30, 10)
var rainbow_sparkles = animation.sparkle_rainbow(60, 40, 30, 10)
```
**`animation.wave_rainbow_sine(amplitude, wave_speed, strip_length, priority)`**
- Creates rainbow sine wave pattern
- **amplitude**: Wave amplitude/intensity (0-255)
- **wave_speed**: Wave movement speed (0-255)
- Returns: `WaveAnimation` instance
**`animation.wave_single_sine(color, amplitude, wave_speed, strip_length, priority)`**
- Creates single-color sine wave
- **color**: ARGB color value or ValueProvider instance
- Returns: `WaveAnimation` instance
**`animation.wave_custom(color, wave_type, amplitude, frequency, strip_length, priority)`**
- Creates custom wave with specified type
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
- **wave_type**: 0=sine, 1=triangle, 2=square, 3=sawtooth
- **frequency**: Wave frequency/density (0-255)
- Returns: `WaveAnimation` instance
```berry
var sine_wave = animation.wave_rainbow_sine(40, 80, 30, 10)
var green_wave = animation.wave_single_sine(0xFF00FF00, 60, 40, 30, 10)
var triangle_wave = animation.wave_custom(nil, 1, 50, 70, 30, 10)
```
### Motion Effect Animations
Motion effects transform existing animations by applying movement, scaling, and distortion effects.
**`animation.shift_scroll_right(source, speed, strip_length, priority)`**
- Scrolls animation to the right with wrapping
- **source**: Source animation to transform
- **speed**: Scroll speed (0-255)
- Returns: `ShiftAnimation` instance
**`animation.shift_scroll_left(source, speed, strip_length, priority)`**
- Scrolls animation to the left with wrapping
- Returns: `ShiftAnimation` instance
**`animation.shift_bounce_horizontal(source, speed, strip_length, priority)`**
- Bounces animation horizontally at strip edges
- Returns: `ShiftAnimation` instance
```berry
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
var scrolling = animation.shift_scroll_right(base, 100, 30, 10)
```
**`animation.bounce_gravity(source, speed, gravity, strip_length, priority)`**
- Physics-based bouncing with gravity simulation
- **source**: Source animation to transform
- **speed**: Initial bounce speed (0-255)
- **gravity**: Gravity strength (0-255)
- Returns: `BounceAnimation` instance
**`animation.bounce_basic(source, speed, damping, strip_length, priority)`**
- Basic bouncing without gravity
- **damping**: Damping factor (0-255, 255=no damping)
- Returns: `BounceAnimation` instance
```berry
var sparkles = animation.sparkle_white(80, 50, 30, 5)
var bouncing = animation.bounce_gravity(sparkles, 150, 80, 30, 10)
var elastic = animation.bounce_basic(sparkles, 120, 240, 30, 10)
```
**`animation.scale_static(source, scale_factor, strip_length, priority)`**
- Static scaling of animation
- **source**: Source animation to transform
- **scale_factor**: Scale factor (128=1.0x, 64=0.5x, 255=2.0x)
- Returns: `ScaleAnimation` instance
**`animation.scale_oscillate(source, speed, strip_length, priority)`**
- Oscillating scale (breathing effect)
- **speed**: Oscillation speed (0-255)
- Returns: `ScaleAnimation` instance
**`animation.scale_grow(source, speed, strip_length, priority)`**
- Growing scale effect
- Returns: `ScaleAnimation` instance
```berry
var pattern = animation.gradient_rainbow_linear(0, 30, 5)
var breathing = animation.scale_oscillate(pattern, 60, 30, 10)
var zoomed = animation.scale_static(pattern, 180, 30, 10) # 1.4x scale
```
**`animation.jitter_position(source, intensity, frequency, strip_length, priority)`**
- Random position shake effects
- **source**: Source animation to transform
- **intensity**: Jitter intensity (0-255)
- **frequency**: Jitter frequency (0-255, maps to 0-30 Hz)
- Returns: `JitterAnimation` instance
**`animation.jitter_color(source, intensity, frequency, strip_length, priority)`**
- Random color variations
- Returns: `JitterAnimation` instance
**`animation.jitter_brightness(source, intensity, frequency, strip_length, priority)`**
- Random brightness changes
- Returns: `JitterAnimation` instance
**`animation.jitter_all(source, intensity, frequency, strip_length, priority)`**
- Combination of position, color, and brightness jitter
- Returns: `JitterAnimation` instance
```berry
var base = animation.gradient_rainbow_linear(0, 30, 5)
var glitch = animation.jitter_all(base, 120, 100, 30, 15)
var shake = animation.jitter_position(base, 60, 40, 30, 10)
```
### Chaining Motion Effects
Motion effects can be chained together for complex transformations:
```berry
# Base animation
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
# Apply multiple transformations
var scaled = animation.scale_static(base, 150, 30, 8) # 1.2x scale
var shifted = animation.shift_scroll_left(scaled, 60, 30, 12) # Scroll left
var jittered = animation.jitter_color(shifted, 40, 30, 30, 15) # Add color jitter
# Result: A scaled, scrolling, color-jittered pulse
```
## Color System
### Color Formats
**ARGB Format**: `0xAARRGGBB`
- **AA**: Alpha channel (opacity) - usually `FF` for opaque
- **RR**: Red component (00-FF)
- **GG**: Green component (00-FF)
- **BB**: Blue component (00-FF)
```berry
var red = 0xFFFF0000 # Opaque red
var semi_blue = 0x800000FF # Semi-transparent blue
var white = 0xFFFFFFFF # Opaque white
var black = 0xFF000000 # Opaque black
```
### Predefined Colors
```berry
# Available as constants
animation.COLOR_RED # 0xFFFF0000
animation.COLOR_GREEN # 0xFF00FF00
animation.COLOR_BLUE # 0xFF0000FF
animation.COLOR_WHITE # 0xFFFFFFFF
animation.COLOR_BLACK # 0xFF000000
```
### Palette System
**Creating Palettes**
```berry
# VRGB format: Value(position), Red, Green, Blue
var fire_palette = bytes("00000000" "80FF0000" "FFFFFF00")
# ^pos=0 ^pos=128 ^pos=255
# black red yellow
```
**Predefined Palettes**
```berry
animation.PALETTE_RAINBOW # Standard rainbow colors
animation.PALETTE_FIRE # Fire effect colors
animation.PALETTE_OCEAN # Ocean wave colors
```
## Value Providers
Dynamic parameters that change over time.
### Static Values
```berry
# Regular values are automatically wrapped
var static_color = 0xFFFF0000
var static_position = 15
```
### Oscillator Providers
**`animation.smooth(start, end, period_ms)`**
- Smooth cosine wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.linear(start, end, period_ms)`**
- Triangle wave oscillation (goes from start to end, then back to start)
- Returns: `OscillatorValueProvider`
**`animation.triangle(start, end, period_ms)`**
- Alias for `linear()` - triangle wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.ramp(start, end, period_ms)`**
- Sawtooth wave oscillation (linear progression from start to end)
- Returns: `OscillatorValueProvider`
**`animation.sawtooth(start, end, period_ms)`**
- Alias for `ramp()` - sawtooth wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.square(start, end, period_ms, duty_cycle=50)`**
- Square wave oscillation
- **duty_cycle**: Percentage of time at high value
- Returns: `OscillatorValueProvider`
```berry
# Dynamic position that moves back and forth
var moving_pos = animation.smooth(0, 29, 3000)
# Dynamic color that cycles brightness
var breathing_color = animation.smooth(50, 255, 2000)
# Use with animations
var dynamic_pulse = animation.pulse_position_animation(
0xFFFF0000, # Static red color
moving_pos, # Dynamic position
3, # Static pulse size
1 # Static slew size
)
```
## Event System
### Event Registration
**`animation.register_event_handler(event_name, callback, priority=0, condition=nil, metadata=nil)`**
- Registers an event handler
- **event_name**: Name of event to handle
- **callback**: Function to call when event occurs
- **priority**: Handler priority (higher = executed first)
- **condition**: Optional condition function
- **metadata**: Optional metadata map
- Returns: `EventHandler` instance
```berry
def flash_white(event_data)
var flash = animation.solid(0xFFFFFFFF)
engine.add_animation(flash)
end
var handler = animation.register_event_handler("button_press", flash_white, 10)
```
### Event Triggering
**`animation.trigger_event(event_name, event_data={})`**
- Triggers an event
- **event_name**: Name of event to trigger
- **event_data**: Data to pass to handlers
```berry
animation.trigger_event("button_press", {"button": "main"})
```
## DSL System
### DSL Runtime
**`animation.DSLRuntime(engine, debug_mode=false)`**
- Creates DSL runtime instance
- **engine**: AnimationEngine instance
- **debug_mode**: Enable debug output
- Returns: `DSLRuntime` instance
#### Methods
**`load_dsl(source_code)`**
- Compiles and executes DSL source code
- **source_code**: DSL source as string
- Returns: `true` on success, `false` on error
**`load_dsl_file(filename)`**
- Loads and executes DSL from file
- **filename**: Path to .anim file
- Returns: `true` on success, `false` on error
```berry
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
var dsl_code = '''
color red = #FF0000
animation pulse_red = pulse(solid(red), 2s, 50%, 100%)
run pulse_red
'''
if runtime.load_dsl(dsl_code)
print("Animation loaded successfully")
else
print("Failed to load animation")
end
```
### DSL Compilation
**`animation.compile_dsl(source_code)`**
- Compiles DSL to Berry code
- **source_code**: DSL source as string
- Returns: Berry code string or raises exception
- Raises: `"dsl_compilation_error"` on compilation failure
```berry
try
var berry_code = animation.compile_dsl(dsl_source)
print("Generated code:", berry_code)
var compiled_func = compile(berry_code)
compiled_func()
except "dsl_compilation_error" as e, msg
print("Compilation error:", msg)
end
```
## User Functions
### Function Registration
**`animation.register_user_function(name, func)`**
- Registers Berry function for DSL use
- **name**: Function name for DSL
- **func**: Berry function to register
**`animation.is_user_function(name)`**
- Checks if function is registered
- Returns: `true` if registered
**`animation.get_user_function(name)`**
- Gets registered function
- Returns: Function or `nil`
**`animation.list_user_functions()`**
- Lists all registered function names
- Returns: Array of function names
```berry
def custom_breathing(color, period)
return animation.pulse(animation.solid(color), period, 50, 255)
end
animation.register_user_function("breathing", custom_breathing)
# Now available in DSL:
# animation my_effect = breathing(red, 3s)
```
## Version Information
The framework uses a numeric version system for efficient comparison:
```berry
# Primary version (0xAABBCCDD format: AA=major, BB=minor, CC=patch, DD=build)
print(f"0x{animation.VERSION:08X}") # 0x00010000
# Convert to string format (drops build number)
print(animation.version_string()) # "0.1.0"
# Convert any version number to string
print(animation.version_string(0x01020304)) # "1.2.3"
# Extract components manually
var major = (animation.VERSION >> 24) & 0xFF # 0
var minor = (animation.VERSION >> 16) & 0xFF # 1
var patch = (animation.VERSION >> 8) & 0xFF # 0
var build = animation.VERSION & 0xFF # 0
# Version comparison
var is_new_enough = animation.VERSION >= 0x00010000 # v0.1.0+
```
## Utility Functions
### Global Variable Access
**`animation.global(name)`**
- Safely accesses global variables
- **name**: Variable name
- Returns: Variable value
- Raises: `"syntax_error"` if variable doesn't exist
```berry
# Set global variable
global.my_color = 0xFFFF0000
# Access safely
var color = animation.global("my_color") # Returns 0xFFFF0000
var missing = animation.global("missing") # Raises exception
```
### Type Checking
**`animation.is_value_provider(obj)`**
- Checks if object is a ValueProvider
- Returns: `true` if object implements ValueProvider interface
**`animation.is_color_provider(obj)`**
- Checks if object is a ColorProvider
- Returns: `true` if object implements ColorProvider interface
```berry
var static_val = 42
var dynamic_val = animation.smooth(0, 100, 2000)
print(animation.is_value_provider(static_val)) # false
print(animation.is_value_provider(dynamic_val)) # true
```
## Error Handling
### Common Exceptions
- **`"dsl_compilation_error"`** - DSL compilation failed
- **`"syntax_error"`** - Variable not found or syntax error
- **`"type_error"`** - Invalid parameter type
- **`"runtime_error"`** - General runtime error
### Best Practices
```berry
# Always use try/catch for DSL operations
try
runtime.load_dsl(dsl_code)
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
except .. as e, msg
print("Unexpected error:", msg)
end
# Check engine state before operations
if engine.is_active()
engine.add_animation(new_animation)
else
print("Engine not running")
end
# Validate parameters
if type(color) == "int" && color >= 0
var anim = animation.solid(color)
else
print("Invalid color value")
end
```
## Performance Tips
### Memory Management
```berry
# Clear animations when switching effects
engine.clear()
engine.add_animation(new_animation)
# Reuse animation objects when possible
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
# Use pulse_red multiple times instead of creating new instances
```
### Timing Optimization
```berry
# Use longer periods for smoother performance
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
var choppy_pulse = animation.pulse(pattern, 100, 50, 255) # 100ms - may be choppy
# Limit simultaneous animations
# Good: 1-3 animations
# Avoid: 10+ animations running simultaneously
```
### Value Provider Efficiency
```berry
# Efficient: Reuse providers
var breathing = animation.smooth(50, 255, 2000)
var anim1 = animation.pulse(pattern1, breathing)
var anim2 = animation.pulse(pattern2, breathing) # Reuse same provider
# Inefficient: Create new providers
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000)) # Duplicate
```
This API reference covers the essential classes and functions. For more advanced usage, see the [Examples](EXAMPLES.md) and [User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md) documentation.

View File

@ -0,0 +1,696 @@
# DSL Reference - Animation DSL Language Specification
This document provides a comprehensive reference for the Animation DSL syntax, keywords, and grammar. It focuses purely on the language specification without implementation details.
## Language Overview
The Animation DSL is a declarative language for defining LED strip animations. It uses natural, readable syntax with named parameters and supports colors, animations, sequences, and property assignments.
## Comments
Comments use the `#` character and extend to the end of the line:
```dsl
# This is a full-line comment
strip length 30 # This is an inline comment
```
Comments are preserved in the generated code and can appear anywhere in the DSL.
## Program Structure
A DSL program consists of statements that can appear in any order:
```dsl
# Optional strip configuration (must be first if present)
strip length 60
# Color definitions
color red = #FF0000
color blue = #0000FF
# Animation definitions
animation pulse_red = pulsating_animation(color=red, period=2s)
# Property assignments
pulse_red.priority = 10
# Sequences
sequence demo {
play pulse_red for 5s
wait 1s
}
# Execution
run demo
```
## Keywords
### Reserved Keywords
The following keywords are reserved and cannot be used as identifiers:
**Configuration Keywords:**
- `strip` - Strip configuration
- `set` - Variable assignment
**Definition Keywords:**
- `color` - Color definition
- `palette` - Palette definition
- `animation` - Animation definition
- `sequence` - Sequence definition
**Control Flow Keywords:**
- `play` - Play animation in sequence
- `wait` - Wait/pause in sequence
- `repeat` - Repeat loop
- `times` - Loop count specifier
- `for` - Duration specifier
- `run` - Execute animation or sequence
**Easing Keywords:**
- `linear` - Linear/triangle wave easing
- `triangle` - Triangle wave easing (alias for linear)
- `smooth` - Smooth cosine easing
- `sine` - Pure sine wave easing
- `ease_in` - Ease in transition (quadratic acceleration)
- `ease_out` - Ease out transition (quadratic deceleration)
- `ramp` - Ramp/sawtooth easing
- `sawtooth` - Sawtooth easing (alias for ramp)
- `square` - Square wave easing
- `elastic` - Elastic easing with spring-like overshoot
- `bounce` - Bounce easing like a ball with decreasing amplitude
**Value Keywords:**
- `true` - Boolean true
- `false` - Boolean false
- `nil` - Null value
- `transparent` - Transparent color
### Predefined Colors
The following color names are predefined and cannot be redefined:
**Primary Colors:**
- `red`, `green`, `blue`
- `white`, `black`
**Extended Colors:**
- `yellow`, `orange`, `purple`, `pink`
- `cyan`, `magenta`, `gray`, `grey`
- `silver`, `gold`, `brown`
- `lime`, `navy`, `olive`
- `maroon`, `teal`, `aqua`
- `fuchsia`, `indigo`, `violet`
- `crimson`, `coral`, `salmon`
- `khaki`, `plum`, `orchid`
- `turquoise`, `tan`, `beige`
- `ivory`, `snow`
- `transparent`
## Data Types
### Numbers
```dsl
42 # Integer
3.14 # Floating point
-5 # Negative number
```
### Time Values
Time values require a unit suffix and are automatically converted to milliseconds:
```dsl
500ms # Milliseconds (stays 500)
2s # Seconds (converted to 2000ms)
1m # Minutes (converted to 60000ms)
1h # Hours (converted to 3600000ms)
```
### Percentages
Percentages use the `%` suffix and are automatically converted to 0-255 range with possible over-shooting:
```dsl
0% # 0 percent (converted to 0)
50% # 50 percent (converted to 128)
100% # 100 percent (converted to 255)
120% # 120 percent (converted to 306)
```
### Colors
#### Hexadecimal Colors
```dsl
0xFF0000 # Red (RGB format)
0x80FF0000 # Semi-transparent red (ARGB format)
```
#### Named Colors
```dsl
red # Predefined color name
blue # Predefined color name
transparent # Transparent color
```
### Strings
```dsl
"hello" # Double-quoted string
'world' # Single-quoted string
```
### Identifiers
Identifiers must start with a letter or underscore, followed by letters, digits, or underscores:
```dsl
my_color # Valid identifier
_private_var # Valid identifier
Color123 # Valid identifier
```
## Configuration Statements
### Strip Configuration
The `strip` statement configures the LED strip and must be the first statement if present:
```dsl
strip length 60 # Set strip length to 60 LEDs
```
If omitted, the system uses the configured strip length from the host system.
### Variable Assignment
The `set` keyword assigns static values or value providers to global variables:
```dsl
set brightness = 200 # Static integer value
set cycle_time = 5s # Static time value (converted to 5000ms)
set opacity_level = 80% # Static percentage (converted to 204)
# Value providers for dynamic values
set brightness_osc = smooth(min_value=50, max_value=255, period=3s)
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
```
## Color Definitions
The `color` keyword defines static colors or color providers:
```dsl
# Static colors
color red = 0xFF0000 # Static hex color
color blue = 0x0000FF # Static hex color
color semi_red = 0x80FF0000 # Static color with alpha channel
color my_white = white # Reference to predefined color
# Color providers for dynamic colors
color rainbow_cycle = color_cycle(
palette=[red, green, blue],
cycle_period=5s
)
color breathing_red = breathe_color(
base_color=red,
min_brightness=20,
max_brightness=255,
duration=3s,
curve_factor=2
)
color pulsing_blue = pulsating_color(
base_color=blue,
min_brightness=50,
max_brightness=200,
duration=1s
)
```
## Palette Definitions
Palettes define color gradients using position-color pairs and support two encoding formats:
### Value-Based Palettes (Recommended)
Standard palettes use value positions from 0-255:
```dsl
palette fire_colors = [
(0, 0x000000), # Position 0: Black
(128, 0xFF0000), # Position 128: Red
(255, 0xFFFF00) # Position 255: Yellow
]
palette ocean_palette = [
(0, navy), # Using named colors
(128, cyan),
(255, green)
]
```
### Tick-Based Palettes (Advanced)
Palettes can also use tick counts for timing-based transitions:
```dsl
palette timed_colors = [
(10, 0xFF0000), # Red for 10 ticks
(20, 0x00FF00), # Green for 20 ticks
(15, 0x0000FF) # Blue for 15 ticks
]
```
**Palette Rules:**
- **Value-based**: Positions range from 0 to 255, represent intensity/brightness levels
- **Tick-based**: Positions represent duration in arbitrary time units
- Colors can be hex values or named colors
- Entries are automatically sorted by position
- Comments are preserved
- Automatically converted to efficient VRGB bytes format
## Animation Definitions
The `animation` keyword defines instances of animation classes (subclasses of Animation):
```dsl
animation red_solid = solid(color=red)
animation pulse_effect = pulsating_animation(
color=blue,
period=2s
)
animation comet_trail = comet_animation(
color=white,
tail_length=10,
speed=1500,
direction=1
)
```
**Parameter Syntax:**
- All parameters use `name=value` format
- Parameters can reference colors, other animations, or literal values
- Nested function calls are supported
## Property Assignments
Animation properties can be modified after creation:
```dsl
animation pulse_red = pulsating_animation(color=red, period=2s)
# Set properties
pulse_red.priority = 10
pulse_red.opacity = 200
pulse_red.position = 15
# Dynamic properties using value providers
pulse_red.position = triangle(min_value=0, max_value=29, period=5s)
pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
```
**Common Properties:**
- `priority` - Animation priority (higher numbers have precedence)
- `opacity` - Opacity level (0-255)
- `position` - Position on strip
- `speed` - Speed multiplier
- `phase` - Phase offset
## Sequences
Sequences orchestrate multiple animations with timing control:
```dsl
sequence demo {
play red_animation for 3s
wait 1s
play blue_animation for 2s
repeat 3 times:
play flash_effect for 200ms
wait 300ms
play final_animation
}
```
### Sequence Statements
#### Play Statement
```dsl
play animation_name # Play indefinitely
play animation_name for 5s # Play for specific duration
```
#### Wait Statement
```dsl
wait 1s # Wait for 1 second
wait 500ms # Wait for 500 milliseconds
```
#### Repeat Statement
```dsl
repeat 5 times:
play effect for 1s
wait 500ms
# Nested repeats are supported
repeat 3 times:
play intro for 2s
repeat 2 times:
play flash for 100ms
wait 200ms
play outro for 1s
```
## Execution Statements
Execute animations or sequences:
```dsl
run animation_name # Run an animation
run sequence_name # Run a sequence
```
## Operators and Expressions
### Arithmetic Operators
```dsl
+ # Addition
- # Subtraction (also unary minus)
* # Multiplication
/ # Division
% # Modulo
```
### Comparison Operators
```dsl
== # Equal to
!= # Not equal to
< # Less than
<= # Less than or equal
> # Greater than
>= # Greater than or equal
```
### Logical Operators
```dsl
&& # Logical AND
|| # Logical OR
! # Logical NOT
```
### Assignment Operators
```dsl
= # Simple assignment
```
## Function Calls
Functions use named parameter syntax:
```dsl
function_name(param1=value1, param2=value2)
# Examples
solid(color=red)
pulsating_animation(color=blue, period=2s)
triangle(min_value=0, max_value=255, period=3s)
```
**Nested Function Calls:**
```dsl
pulsating_animation(
color=solid(color=red),
period=smooth(min_value=1000, max_value=3000, period=10s)
)
```
## Supported Classes
### Value Providers
Value providers create dynamic values that change over time:
| Function | Description |
|----------|-------------|
| `static_value` | Returns a constant value |
| `oscillator_value` | Oscillates between min/max values with various waveforms |
**Oscillator Aliases:**
| Function | Description |
|----------|-------------|
| `triangle` | Triangle wave oscillation (alias for oscillator with triangle waveform) |
| `smooth` | Smooth cosine wave (alias for oscillator with smooth waveform) |
| `sine` | Pure sine wave oscillation (alias for oscillator with sine waveform) |
| `linear` | Linear progression (alias for oscillator with linear waveform) |
| `ramp` | Sawtooth wave (alias for oscillator with ramp waveform) |
| `sawtooth` | Sawtooth wave (alias for ramp) |
| `square` | Square wave oscillation |
| `ease_in` | Quadratic ease-in (starts slow, accelerates) |
| `ease_out` | Quadratic ease-out (starts fast, decelerates) |
| `elastic` | Elastic easing with spring-like overshoot |
| `bounce` | Bounce easing like a ball with decreasing amplitude |
```dsl
# Direct oscillator usage
triangle(min_value=0, max_value=255, period=2s) # Triangle wave
smooth(min_value=50, max_value=200, period=3s) # Smooth cosine
sine(min_value=0, max_value=255, period=2s) # Pure sine wave
linear(min_value=0, max_value=100, period=1s) # Linear progression
ramp(min_value=0, max_value=255, period=2s) # Sawtooth wave
square(min_value=0, max_value=255, period=1s) # Square wave
ease_in(min_value=0, max_value=255, period=2s) # Quadratic ease-in
ease_out(min_value=0, max_value=255, period=2s) # Quadratic ease-out
elastic(min_value=0, max_value=255, period=2s) # Elastic spring effect
bounce(min_value=0, max_value=255, period=2s) # Bouncing ball effect
# Value providers can be assigned to variables
set brightness_oscillator = smooth(min_value=50, max_value=255, period=3s)
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
set elastic_movement = elastic(min_value=0, max_value=30, period=4s)
```
### Color Providers
Color providers create dynamic colors that change over time:
| Function | Description |
|----------|-------------|
| `static_color` | Solid color with optional dynamic opacity |
| `color_cycle` | Cycles through a palette of colors |
| `rich_palette` | Advanced palette-based color cycling with smooth transitions |
| `composite_color` | Combines multiple color providers |
| `breathe_color` | Breathing/pulsing color effect with brightness modulation |
| `pulsating_color` | Fast pulsing color effect (alias for breathe_color with curve_factor=1) |
### Animation Classes
Animation classes create visual effects on LED strips:
| Function | Description |
|----------|-------------|
| `solid` | Solid color fill |
| `pulsating_animation` | Pulsing brightness effect |
| `beacon_animation` | Positioned pulse effect |
| `crenel_position_animation` | Square wave pulse at specific position |
| `breathe_animation` | Breathing/fading effect |
| `comet_animation` | Moving comet with trailing tail |
| `fire_animation` | Realistic fire simulation |
| `twinkle_animation` | Twinkling stars effect |
| `gradient_animation` | Color gradient effects |
| `noise_animation` | Perlin noise-based patterns |
| `plasma_animation` | Plasma wave effects |
| `sparkle_animation` | Sparkling/glitter effects |
| `wave_animation` | Wave propagation effects |
| `shift_animation` | Shifting/scrolling patterns |
| `bounce_animation` | Bouncing ball effects |
| `scale_animation` | Scaling/zooming effects |
| `jitter_animation` | Random jitter/shake effects |
| `rich_palette_animation` | Palette-based color cycling |
| `palette_wave_animation` | Wave patterns using palettes |
| `palette_gradient_animation` | Gradient patterns using palettes |
| `palette_meter_animation` | Meter/bar patterns using palettes |
## Error Handling
### Validation Rules
The DSL performs comprehensive validation at compile time:
1. **Reserved Names**: Cannot redefine keywords or predefined colors
2. **Class Existence**: Animation and color provider factory functions must exist
3. **Parameter Validation**: Function parameters must exist and be valid for the specific class
4. **Type Checking**: Values must match expected types
5. **Reference Resolution**: All referenced identifiers must be defined
### Compile-Time Validation
The DSL validates class and parameter existence during compilation, catching errors before execution:
- **Factory Functions**: Verifies that animation and color provider factories exist in the animation module
- **Parameter Names**: Checks that all named parameters are valid for the specific class
- **Parameter Constraints**: Validates parameter values against defined constraints (min/max, enums, types)
- **Nested Validation**: Validates parameters in nested function calls and value providers
### Common Errors
```dsl
# Invalid: Redefining predefined color
color red = 0x800000 # Error: Cannot redefine 'red'
# Invalid: Unknown parameter
animation bad = pulsating_animation(invalid_param=123) # Error: Unknown parameter
# Invalid: Undefined reference
animation ref = solid(color=undefined_color) # Error: Undefined reference
# Valid alternatives
color my_red = 0x800000 # OK: Different name
animation good = pulsating_animation(color=red, period=2s) # OK: Valid parameters
```
## Formal Grammar (EBNF)
```ebnf
(* Animation DSL Grammar *)
program = { statement } ;
statement = config_stmt
| definition
| property_assignment
| sequence
| execution_stmt ;
(* Configuration *)
config_stmt = strip_config | variable_assignment ;
strip_config = "strip" "length" number ;
variable_assignment = "set" identifier "=" expression ;
(* Definitions *)
definition = color_def | palette_def | animation_def ;
color_def = "color" identifier "=" color_expression ;
palette_def = "palette" identifier "=" palette_array ;
animation_def = "animation" identifier "=" animation_expression ;
(* Property Assignments *)
property_assignment = identifier "." identifier "=" expression ;
(* Sequences *)
sequence = "sequence" identifier "{" sequence_body "}" ;
sequence_body = { sequence_statement } ;
sequence_statement = play_stmt | wait_stmt | repeat_stmt ;
play_stmt = "play" identifier [ "for" time_expression ] ;
wait_stmt = "wait" time_expression ;
repeat_stmt = "repeat" number "times" ":" sequence_body ;
(* Execution *)
execution_stmt = "run" identifier ;
(* Expressions *)
expression = logical_or_expr ;
logical_or_expr = logical_and_expr { "||" logical_and_expr } ;
logical_and_expr = equality_expr { "&&" equality_expr } ;
equality_expr = relational_expr { ( "==" | "!=" ) relational_expr } ;
relational_expr = additive_expr { ( "<" | "<=" | ">" | ">=" ) additive_expr } ;
additive_expr = multiplicative_expr { ( "+" | "-" ) multiplicative_expr } ;
multiplicative_expr = unary_expr { ( "*" | "/" | "%" ) unary_expr } ;
unary_expr = ( "!" | "-" | "+" ) unary_expr | primary_expr ;
primary_expr = literal | identifier | function_call | "(" expression ")" ;
(* Color Expressions *)
color_expression = hex_color | named_color | identifier ;
hex_color = "0x" hex_digit{6} | "0x" hex_digit{8} ;
named_color = color_name ;
(* Animation Expressions *)
animation_expression = function_call | identifier ;
(* Palette Arrays *)
palette_array = "[" palette_entry { "," palette_entry } "]" ;
palette_entry = "(" number "," color_expression ")" ;
(* Function Calls *)
function_call = identifier "(" [ named_argument_list ] ")" ;
named_argument_list = named_argument { "," named_argument } ;
named_argument = identifier "=" expression ;
(* Time Expressions *)
time_expression = time_literal ;
time_literal = number time_unit ;
time_unit = "ms" | "s" | "m" | "h" ;
(* Literals *)
literal = number | string | color_expression | time_expression | percentage | boolean ;
number = integer | real ;
integer = [ "-" ] digit { digit } ;
real = [ "-" ] digit { digit } "." digit { digit } ;
string = '"' { string_char } '"' | "'" { string_char } "'" ;
percentage = number "%" ;
boolean = "true" | "false" ;
(* Identifiers *)
identifier = ( letter | "_" ) { letter | digit | "_" } ;
color_name = "red" | "green" | "blue" | "white" | "black" | "yellow"
| "orange" | "purple" | "pink" | "cyan" | "magenta" | "gray"
| "silver" | "gold" | "brown" | "lime" | "navy" | "olive"
| "maroon" | "teal" | "aqua" | "fuchsia" | "transparent" ;
(* Character Classes *)
letter = "a" .. "z" | "A" .. "Z" ;
digit = "0" .. "9" ;
hex_digit = digit | "A" .. "F" | "a" .. "f" ;
string_char = (* any character except quote *) ;
(* Comments and Whitespace *)
comment = "#" { (* any character except newline *) } newline ;
whitespace = " " | "\t" | "\r" | "\n" ;
newline = "\n" | "\r\n" ;
```
## Language Features Summary
### ✅ Currently Implemented
- Comments with preservation
- Strip configuration (optional)
- Color definitions (hex and named)
- Palette definitions with VRGB conversion
- Animation definitions with named parameters
- Property assignments
- Basic sequences (play, wait, repeat)
- Variable assignments with type conversion
- Reserved name validation
- Parameter validation at compile time
- Execution statements
- User-defined functions (with engine-first parameter pattern) - see **[User Functions Guide](USER_FUNCTIONS.md)**
### 🚧 Partially Implemented
- Expression evaluation (basic support)
- Nested function calls (working but limited)
- Error recovery (basic error reporting)
### ❌ Planned Features
- Advanced control flow (if/else, choose random)
- Event system and handlers
- Mathematical expressions
- Variable references with $ syntax
- Spatial operations and zones
- 2D matrix support
This reference provides the complete syntax specification for the Animation DSL language as currently implemented and planned for future development.

View File

@ -0,0 +1,434 @@
# 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)
## 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 = #FF0000\n"
"animation red_anim = solid(color=red)\n"
"run red_anim"
var berry_code = animation_dsl.compile(dsl_source)
print(berry_code) # Shows generated Berry code
```
#### `animation_dsl.execute(source)`
Compiles and executes DSL source code in one step.
```berry
animation_dsl.execute("color blue = #0000FF\n"
"animation blue_anim = solid(color=blue)\n"
"run blue_anim for 5s")
```
#### `animation_dsl.load_file(filename)`
Loads DSL source from a file and executes it.
```berry
# Create a DSL file
var f = open("my_animation.dsl", "w")
f.write("color green = #00FF00\n"
"animation pulse_green = pulsating_animation(color=green, period=2s)\n"
"run pulse_green")
f.close()
# Load and execute
animation_dsl.load_file("my_animation.dsl")
```
### Runtime Management
#### `animation_dsl.create_runtime()`
Creates a DSL runtime instance for advanced control.
```berry
var runtime = animation_dsl.create_runtime()
runtime.load_dsl(dsl_source)
runtime.execute()
```
## DSL Language Overview
The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility.
### Key Syntax Features
- **Named parameters**: All function calls use `name=value` syntax
- **Time units**: `2s`, `500ms`, `1m`, `1h`
- **Hex colors**: `#FF0000`, `#80FF0000` (ARGB)
- **Named colors**: `red`, `blue`, `white`, etc.
- **Comments**: `# This is a comment`
- **Property assignment**: `animation.property = value`
### Basic Structure
```dsl
# Optional strip configuration
strip length 60
# Color definitions
color red = #FF0000
color blue = #0000FF
# Animation definitions with named parameters
animation pulse_red = pulsating_animation(color=red, period=2s)
animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
# Property assignments
pulse_red.priority = 10
comet_blue.direction = -1
# Execution
run pulse_red
```
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
## Advanced DSL Features
### User-Defined Functions
Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments:
```berry
# Define custom function in Berry - engine must be first parameter
def custom_sparkle(engine, color, density, speed)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.density = density
anim.speed = speed
return anim
end
# Register the function for DSL use
animation.register_user_function("sparkle", custom_sparkle)
```
```dsl
# Use in DSL - engine is automatically passed as first argument
animation gold_sparkle = sparkle(#FFD700, 8, 500ms)
animation blue_sparkle = sparkle(blue, 12, 300ms)
run gold_sparkle
```
**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function.
For comprehensive examples and best practices, see the **[User Functions Guide](USER_FUNCTIONS.md)**.
### Event System
Define event handlers that respond to triggers:
```dsl
# Define animations for different states
color normal = #000080
color alert = #FF0000
animation normal_state = solid(color=normal)
animation alert_state = pulsating_animation(color=alert, period=500ms)
# Event handlers
on button_press {
run alert_state for 3s
run normal_state
}
on sensor_trigger {
run alert_state for 5s
wait 1s
run normal_state
}
# Default state
run normal_state
```
### Nested Function Calls
DSL supports nested function calls for complex compositions:
```dsl
# Nested calls in animation definitions (now supported)
animation complex = pulsating_animation(
source=shift_animation(
source=solid(color=red),
offset=triangle(min=0, max=29, period=3s)
),
period=2s
)
# Nested calls in run statements
sequence demo {
play pulsating_animation(source=shift_animation(source=solid(color=blue), offset=5), period=1s) for 10s
}
```
## Error Handling
The DSL compiler validates classes and parameters at transpilation time, catching errors before execution:
```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:**
```dsl
# 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:**
```dsl
# Error: Invalid parameter name
animation pulse = pulsating_animation(invalid_param=123)
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
# Error: Parameter constraint violation
animation comet = comet_animation(tail_length=-5)
# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1"
```
**Color Provider Validation:**
```dsl
# 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:**
```dsl
# Error: Undefined color reference
animation pulse = pulsating_animation(color=undefined_color)
# Transpiler error: "Undefined reference: 'undefined_color'"
# Error: Undefined animation reference
run nonexistent_animation
# Transpiler error: "Undefined reference: 'nonexistent_animation'"
```
### Error Categories
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
- **Factory validation**: Non-existent or invalid animation/color provider factories
- **Parameter validation**: Invalid parameter names or constraint violations
- **Reference validation**: Using undefined colors, animations, or variables
- **Type validation**: Incorrect parameter types or incompatible assignments
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
## Performance Considerations
### DSL vs Programmatic Performance
- **DSL compilation overhead**: ~10-50ms depending on complexity
- **Generated code performance**: Identical to hand-written Berry code
- **Memory usage**: DSL compiler uses temporary memory during compilation
### Optimization Tips
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 {
play performance_critical_anim for 10s
}
run main
''')
# Programmatic for performance-critical animations
var performance_critical_anim = animation.create_optimized_animation()
```
3. **Minimize DSL recompilation**:
```berry
# Good: Compile once
var runtime = animation_dsl.create_runtime()
runtime.load_dsl(source)
runtime.execute()
# Avoid: Recompiling same DSL repeatedly
# animation_dsl.execute(same_source) # Don't do this in loops
```
## Integration Examples
### With Tasmota Rules
```berry
# In autoexec.be
import animation
import animation_dsl
def handle_rule_trigger(event)
if event == "motion"
animation_dsl.execute("color alert = #FF0000\n"
"animation alert_anim = pulsating_animation(color=alert, period=500ms)\n"
"run alert_anim for 5s")
elif event == "door"
animation_dsl.execute("color welcome = #00FF00\n"
"animation welcome_anim = breathe_animation(color=welcome, period=2s)\n"
"run welcome_anim for 8s")
end
end
# Register with Tasmota's rule system
tasmota.add_rule("motion", handle_rule_trigger)
```
### With Web Interface
```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**:
```dsl
# Strip configuration first
strip length 60
# Colors next
color red = #FF0000
color blue = #0000FF
# Animations with named parameters
animation red_solid = solid(color=red)
animation pulse_red = pulsating_animation(color=red, period=2s)
# Property assignments
pulse_red.priority = 10
# Sequences
sequence demo {
play pulse_red for 5s
}
# Execution last
run demo
```
2. **Use meaningful names**:
```dsl
# Good
color warning_red = #FF0000
animation door_alert = pulsating_animation(color=warning_red, period=500ms)
# Avoid
color c1 = #FF0000
animation a1 = pulsating_animation(color=c1, period=500ms)
```
3. **Comment your DSL**:
```dsl
# Security system colors
color normal_blue = #000080 # Idle state
color alert_red = #FF0000 # Alert state
color success_green = #00FF00 # Success state
# Main security animation sequence
sequence security_demo {
play solid(color=normal_blue) for 10s # Normal operation
play pulsating_animation(color=alert_red, period=500ms) for 3s # Alert
play breathe_animation(color=success_green, period=2s) for 5s # Success confirmation
}
```
4. **Organize complex projects**:
```berry
# Load DSL modules
animation_dsl.load_file("colors.dsl") # Color definitions
animation_dsl.load_file("animations.dsl") # Animation library
animation_dsl.load_file("sequences.dsl") # Sequence definitions
animation_dsl.load_file("main.dsl") # Main execution
```
This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed.

View File

@ -1,568 +1,176 @@
# Examples
Curated examples showcasing the Tasmota Berry Animation Framework capabilities.
Essential examples showcasing the Tasmota Berry Animation Framework using DSL syntax.
## Basic Examples
## Basic Animations
### 1. Simple Solid Color
**Berry Code:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
# Create solid red animation
var red = animation.solid(0xFFFF0000)
engine.add_animation(red).start()
```
**DSL Version:**
### 1. Solid Color
```dsl
color red = #FF0000
animation solid_red = solid(red)
run solid_red
color red = 0xFF0000
animation red_solid = solid(color=red)
run red_solid
```
### 2. Pulsing Effect
**Berry Code:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
# Create pulsing blue animation
var pulse_blue = animation.pulse(
animation.solid(0xFF0000FF), # Blue color
3000, # 3 second period
50, # Min brightness
255 # Max brightness
)
engine.add_animation(pulse_blue).start()
```dsl
color blue = 0x0000FF
animation blue_pulse = pulsating_animation(color=blue, period=2s)
run blue_pulse
```
**DSL Version:**
### 3. Moving Comet
```dsl
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 3s, 20%, 100%)
run pulse_blue
color cyan = 0x00FFFF
animation comet_trail = comet_animation(color=cyan, tail_length=8, speed=100ms, direction=1)
run comet_trail
```
### 3. Breathing Effect
## Using Value Providers
**DSL:**
### 4. Breathing Effect
```dsl
color soft_white = #C0C0C0
animation breathing = breathe(soft_white, 4s)
run breathing
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
```
## Color and Palette Examples
### 4. Fire Effect
**DSL:**
### 5. Color Cycling
```dsl
# Define fire palette
palette fire_colors = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF8000), # Orange
(255, #FFFF00) # Yellow
]
# Create fire animation
animation fire_effect = rich_palette_animation(fire_colors, 2s, smooth, 255)
run fire_effect
```
### 5. Rainbow Cycle
**DSL:**
```dsl
palette rainbow = [
(0, red), (42, orange), (84, yellow),
(126, green), (168, blue), (210, indigo), (255, violet)
]
animation rainbow_cycle = rich_palette_animation(rainbow, 8s, smooth, 255)
color rainbow = rainbow_color_provider(period=5s)
animation rainbow_cycle = solid(color=rainbow)
run rainbow_cycle
```
### 6. Ocean Waves
## Palette Animations
**DSL:**
### 6. Fire Effect
```dsl
palette ocean = [
(0, navy), # Deep ocean
(64, blue), # Ocean blue
(128, cyan), # Shallow water
(192, #87CEEB), # Sky blue
(255, white) # Foam
palette fire_colors = [
(0, 0x000000), # Black
(128, 0xFF0000), # Red
(192, 0xFF8000), # Orange
(255, 0xFFFF00) # Yellow
]
animation ocean_waves = rich_palette_animation(ocean, 6s, smooth, 200)
run ocean_waves
animation fire_effect = palette_animation(palette=fire_colors, period=2s, intensity=255)
run fire_effect
```
## Position-Based Examples
## Sequences
### 7. Center Pulse
**DSL:**
### 7. RGB Show
```dsl
strip length 60
color white = #FFFFFF
color red = 0xFF0000
color green = 0x00FF00
color blue = 0x0000FF
# Pulse at center position
animation center_pulse = pulse_position_animation(white, 30, 5, 3)
run center_pulse
```
animation red_anim = solid(color=red)
animation green_anim = solid(color=green)
animation blue_anim = solid(color=blue)
### 8. Moving Comet
**Berry Code:**
```berry
import animation
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Create cyan comet with 8-pixel tail
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
engine.add_animation(comet).start()
```
### 9. Twinkling Stars
**DSL:**
```dsl
color star_white = #FFFFFF
animation stars = twinkle_animation(star_white, 8, 500ms)
run stars
```
## Dynamic Parameter Examples
### 10. Moving Pulse
**Berry Code:**
```berry
import animation
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Create dynamic position that moves back and forth
var moving_pos = animation.smooth(5, 55, 4000) # 4-second cycle
# Create pulse with dynamic position
var moving_pulse = animation.pulse_position_animation(
0xFFFF0000, # Red color
moving_pos, # Dynamic position
3, # Pulse size
2 # Fade size
)
engine.add_animation(moving_pulse).start()
```
### 11. Color-Changing Pulse
**Berry Code:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
# Create color cycle provider
var color_cycle = animation.color_cycle_color_provider(
[0xFFFF0000, 0xFF00FF00, 0xFF0000FF], # Red, Green, Blue
5000, # 5-second cycle
1 # Smooth transitions
)
# Create filled animation with dynamic color
var color_changing = animation.filled(color_cycle, 0, 0, true, "color_cycle")
engine.add_animation(color_changing).start()
```
### 12. Breathing Size
**Berry Code:**
```berry
import animation
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Dynamic pulse size that breathes
var breathing_size = animation.smooth(1, 10, 3000)
# Pulse with breathing size
var breathing_pulse = animation.pulse_position_animation(
0xFF8000FF, # Purple color
30, # Center position
breathing_size, # Dynamic size
1 # Fade size
)
engine.add_animation(breathing_pulse).start()
```
## Sequence Examples
### 13. RGB Show
**DSL:**
```dsl
# Define colors
color red = #FF0000
color green = #00FF00
color blue = #0000FF
# Create animations
animation red_pulse = pulse(solid(red), 2s, 50%, 100%)
animation green_pulse = pulse(solid(green), 2s, 50%, 100%)
animation blue_pulse = pulse(solid(blue), 2s, 50%, 100%)
# Create sequence
sequence rgb_show {
play red_pulse for 3s
wait 500ms
play green_pulse for 3s
wait 500ms
play blue_pulse for 3s
wait 1s
repeat 3 times:
play red_pulse for 1s
play green_pulse for 1s
play blue_pulse for 1s
play red_anim for 2s
play green_anim for 2s
play blue_anim for 2s
}
run rgb_show
```
### 14. Sunrise Sequence
**DSL:**
### 8. Sunrise Sequence
```dsl
# Define sunrise colors
color deep_blue = #000080
color purple = #800080
color pink = #FF69B4
color orange = #FFA500
color yellow = #FFFF00
color deep_blue = 0x000080
color orange = 0xFFA500
color yellow = 0xFFFF00
# Create animations
animation night = solid(deep_blue)
animation dawn = pulse(solid(purple), 4s, 30%, 100%)
animation sunrise = pulse(solid(pink), 3s, 50%, 100%)
animation morning = pulse(solid(orange), 2s, 70%, 100%)
animation day = solid(yellow)
animation night = solid(color=deep_blue)
animation sunrise = pulsating_animation(color=orange, period=3s)
animation day = solid(color=yellow)
# Sunrise sequence
sequence sunrise_show {
play night for 2s
play dawn for 8s
play sunrise for 6s
play morning for 4s
play day for 5s
play night for 3s
play sunrise for 5s
play day for 3s
}
run sunrise_show
```
### 15. Party Mode
**DSL:**
```dsl
# Party colors
color hot_pink = #FF1493
color lime = #00FF00
color cyan = #00FFFF
color magenta = #FF00FF
# Fast animations
animation pink_flash = pulse(solid(hot_pink), 500ms, 80%, 100%)
animation lime_flash = pulse(solid(lime), 600ms, 80%, 100%)
animation cyan_flash = pulse(solid(cyan), 400ms, 80%, 100%)
animation magenta_flash = pulse(solid(magenta), 700ms, 80%, 100%)
# Party sequence
sequence party_mode {
repeat 10 times:
play pink_flash for 1s
play lime_flash for 1s
play cyan_flash for 800ms
play magenta_flash for 1200ms
}
run party_mode
```
## Interactive Examples
### 16. Button-Controlled Colors
**DSL:**
```dsl
# Define colors
color red = #FF0000
color green = #00FF00
color blue = #0000FF
color white = #FFFFFF
# Define animations
animation red_glow = solid(red)
animation green_glow = solid(green)
animation blue_glow = solid(blue)
animation white_flash = pulse(solid(white), 500ms, 50%, 100%)
# Event handlers
on button_press: white_flash
on timer(5s): red_glow
on timer(10s): green_glow
on timer(15s): blue_glow
# Default animation
run red_glow
```
### 17. Brightness-Responsive Animation
**Berry Code:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
# Brightness-responsive handler
def brightness_handler(event_data)
var brightness = event_data.find("brightness", 128)
if brightness > 200
# Bright environment - subtle colors
var subtle = animation.solid(0xFF404040) # Dim white
engine.clear()
engine.add_animation(subtle)
elif brightness > 100
# Medium light - normal colors
var normal = animation.pulse(animation.solid(0xFF0080FF), 3000, 100, 255)
engine.clear()
engine.add_animation(normal)
else
# Dark environment - bright colors
var bright = animation.pulse(animation.solid(0xFFFFFFFF), 2000, 200, 255)
engine.clear()
engine.add_animation(bright)
end
end
# Register brightness handler
animation.register_event_handler("brightness_change", brightness_handler, 5)
# Start with default animation
var default_anim = animation.pulse(animation.solid(0xFF8080FF), 3000, 100, 255)
engine.add_animation(default_anim).start()
```
## Advanced Examples
### 18. Aurora Borealis
**DSL:**
### 9. Dynamic Position
```dsl
strip length 60
# Aurora palette with ethereal colors
palette aurora = [
(0, #000022), # Dark night sky
(32, #001144), # Deep blue
(64, #004400), # Dark green
(96, #006633), # Forest green
(128, #00AA44), # Aurora green
(160, #44AA88), # Light green
(192, #66CCAA), # Pale green
(224, #88FFCC), # Bright aurora
(255, #AAFFDD) # Ethereal glow
]
set moving_position = smooth(min_value=5, max_value=55, period=4s)
color purple = 0x8000FF
# Slow, ethereal aurora animation
animation aurora_borealis = rich_palette_animation(aurora, 12s, smooth, 180)
# Set properties for mystical effect
aurora_borealis.priority = 10
aurora_borealis.opacity = 220
run aurora_borealis
animation moving_pulse = beacon_animation(
color=purple,
position=moving_position,
beacon_size=3,
fade_size=2
)
run moving_pulse
```
### 19. Campfire Simulation
**Berry Code:**
```berry
import animation
var strip = Leds(40)
var engine = animation.create_engine(strip)
# Create fire animation with realistic parameters
var fire = animation.fire_animation(180, 120) # Medium intensity, moderate speed
# Add some twinkling embers
var embers = animation.twinkle_animation(0xFFFF4500, 3, 800) # Orange embers
embers.set_priority(5) # Lower priority than fire
embers.set_opacity(150) # Semi-transparent
# Combine fire and embers
engine.add_animation(fire)
engine.add_animation(embers)
engine.start()
```
### 20. User-Defined Function Example
**Berry Code:**
```berry
import animation
# Define custom breathing effect
def custom_breathing(base_color, period, min_percent, max_percent)
var min_brightness = int(tasmota.scale_uint(min_percent, 0, 100, 0, 255))
var max_brightness = int(tasmota.scale_uint(max_percent, 0, 100, 0, 255))
return animation.pulse(
animation.solid(base_color),
period,
min_brightness,
max_brightness
)
end
# Register the function
animation.register_user_function("breathing", custom_breathing)
# Now use in DSL
var dsl_code = '''
color soft_blue = #4080FF
animation calm_breathing = breathing(soft_blue, 4000, 10, 90)
run calm_breathing
'''
var strip = Leds(30)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
runtime.load_dsl(dsl_code)
```
## Performance Examples
### 21. Efficient Multi-Animation
**Berry Code:**
```berry
import animation
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Create shared value providers for efficiency
var slow_breathing = animation.smooth(100, 255, 4000)
var position_sweep = animation.linear(5, 55, 6000)
# Create multiple animations using shared providers
var pulse1 = animation.pulse_position_animation(0xFFFF0000, 15, 3, 1)
pulse1.set_opacity(slow_breathing) # Shared breathing effect
var pulse2 = animation.pulse_position_animation(0xFF00FF00, 30, 3, 1)
pulse2.set_opacity(slow_breathing) # Same breathing effect
var pulse3 = animation.pulse_position_animation(0xFF0000FF, 45, 3, 1)
pulse3.set_opacity(slow_breathing) # Same breathing effect
# Add all animations
engine.add_animation(pulse1)
engine.add_animation(pulse2)
engine.add_animation(pulse3)
engine.start()
```
### 22. Memory-Efficient Palette Cycling
**DSL:**
### 10. Multi-Layer Effect
```dsl
# Define single palette for multiple uses
palette shared_rainbow = [
(0, red), (51, orange), (102, yellow),
(153, green), (204, blue), (255, violet)
]
# 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
# Create multiple animations with different speeds using same palette
animation fast_rainbow = rich_palette_animation(shared_rainbow, 3s, smooth, 255)
animation slow_rainbow = rich_palette_animation(shared_rainbow, 10s, smooth, 180)
# Accent layer - twinkling stars
color star_white = 0xFFFFFF
animation stars = twinkle_animation(color=star_white, count=5, period=800ms)
stars.opacity = 150
# Use in sequence to avoid simultaneous memory usage
sequence efficient_show {
play fast_rainbow for 15s
wait 1s
play slow_rainbow for 20s
sequence layered_effect {
play base_layer for 10s
play stars for 10s
}
run efficient_show
run layered_effect
```
## Tips for Creating Your Own Examples
## Tips for Creating Animations
### 1. Start Simple
Begin with basic solid colors and simple pulses before adding complexity.
### 2. Use Meaningful Names
### Start Simple
```dsl
# Good
color sunset_orange = #FF8C00
animation evening_glow = pulse(solid(sunset_orange), 4s, 30%, 100%)
# Less clear
color c1 = #FF8C00
animation a1 = pulse(solid(c1), 4s, 30%, 100%)
# Begin with basic colors and effects
color my_color = 0xFF0000
animation simple = solid(color=my_color)
run simple
```
### 3. Comment Your Code
### Use Meaningful Names
```dsl
# Sunrise simulation - starts dark and gradually brightens
palette sunrise_colors = [
(0, #000033), # Pre-dawn darkness
(64, #663366), # Purple twilight
(128, #CC6633), # Orange sunrise
(255, #FFFF99) # Bright morning
]
# 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)
```
### 4. Test Incrementally
Build complex animations step by step:
1. Test basic colors
2. Add simple effects
3. Combine with sequences
4. Add interactivity
### Test Incrementally
1. Start with solid colors
2. Add simple effects like pulse
3. Experiment with sequences
4. Combine multiple animations
### 5. Consider Performance
- Limit simultaneous animations (3-5 max)
- Use longer periods for smoother performance
- Reuse value providers when possible
- Clear animations when switching effects
### 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
## Next Steps
- **[API Reference](API_REFERENCE.md)** - Complete API documentation
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - DSL syntax guide
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom functions
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Interactive animations
- **[DSL Reference](DSL_REFERENCE.md)** - Complete language syntax
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
- **[Animation Development](ANIMATION_DEVELOPMENT.md)** - Creating custom animations
Experiment with these examples and create your own amazing LED animations! 🎨✨
Start with these examples and build your own amazing LED animations! 🎨✨

View File

@ -0,0 +1,262 @@
# 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
```dsl
# 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
```dsl
# 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(min_value=50, max_value=255, duration=3000) # form=SINE
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
```dsl
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
```
```dsl
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
```dsl
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
```
```dsl
set wave_motion = sine(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
```
```dsl
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
```
```dsl
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
```dsl
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
```dsl
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
```dsl
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
```dsl
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
```dsl
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
```dsl
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
```dsl
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
```dsl
color white = 0xFFFFFF
set strobe_pattern = square(min_value=0, max_value=255, duration=200, duty_cycle=10)
animation strobe_light = solid(color=white)
strobe_light.opacity = strobe_pattern
run strobe_light
```
## Tips
- **COSINE (smooth)**: Most natural for breathing and gentle effects
- **SINE**: Classic wave motion, perfect for audio-visual sync and pure oscillations
- **SAWTOOTH (linear)**: Best for consistent sweeps and mechanical movements
- **TRIANGLE**: Creates sharp, bouncing transitions
- **EASE_IN**: Perfect for building up intensity
- **EASE_OUT**: Ideal for gentle fade-outs
- **ELASTIC**: Spring-like effects with overshoot
- **BOUNCE**: Physics-based bouncing effects
- **SQUARE**: Good for on/off blinking effects
Choose the oscillation pattern that matches the feeling you want to create in your animation.

View File

@ -1,231 +0,0 @@
# Project Structure
This document explains the organization of the Tasmota Berry Animation Framework project.
## Root Directory
```
├── README.md # Main project overview and quick start
├── docs/ # User documentation
├── lib/libesp32/berry_animation/ # Framework source code
├── anim_examples/ # DSL animation examples (.anim files)
├── anim_examples/compiled/ # Compiled Berry code from DSL examples
├── .kiro/ # Project specifications and design docs
└── .docs_archive/ # Archived technical implementation docs
```
## Documentation (`docs/`)
User-focused documentation for learning and using the framework:
```
docs/
├── QUICK_START.md # 5-minute getting started guide
├── API_REFERENCE.md # Complete Berry API documentation
├── EXAMPLES.md # Curated examples with explanations
├── TROUBLESHOOTING.md # Common issues and solutions
└── PROJECT_STRUCTURE.md # This file
```
## Framework Source Code (`lib/libesp32/berry_animation/`)
The complete framework implementation:
```
lib/libesp32/berry_animation/
├── animation.be # Main module entry point
├── README.md # Framework-specific readme
├── user_functions.be # Example user-defined functions
├── core/ # Core framework classes
│ ├── animation_base.be # Animation base class
│ ├── animation_engine.be # Unified animation engine
│ ├── event_handler.be # Event system
│ ├── frame_buffer.be # Frame buffer management
│ ├── pattern_base.be # Pattern base class
│ ├── sequence_manager.be # Sequence orchestration
│ └── user_functions.be # User function registry
├── effects/ # Animation implementations
│ ├── breathe.be # Breathing animation
│ ├── comet.be # Comet effect
│ ├── crenel_position.be # Rectangular pulse patterns
│ ├── filled.be # Filled animations
│ ├── fire.be # Fire simulation
│ ├── palette_pattern.be # Palette-based patterns
│ ├── palettes.be # Predefined palettes
│ ├── pattern_animation.be # Pattern wrapper animation
│ ├── pulse.be # Pulse animation
│ ├── pulse_position.be # Position-based pulse
│ └── twinkle.be # Twinkling stars
├── patterns/ # Pattern implementations
│ └── solid_pattern.be # Solid color pattern
├── providers/ # Value and color providers
│ ├── color_cycle_color_provider.be # Color cycling
│ ├── color_provider.be # Base color provider
│ ├── composite_color_provider.be # Blended colors
│ ├── oscillator_value_provider.be # Waveform generators
│ ├── rich_palette_color_provider.be # Palette-based colors
│ ├── solid_color_provider.be # Static colors
│ ├── static_value_provider.be # Static value wrapper
│ └── value_provider.be # Base value provider
├── dsl/ # Domain-Specific Language
│ ├── lexer.be # DSL tokenizer
│ ├── runtime.be # DSL execution runtime
│ ├── token.be # Token definitions
│ └── transpiler.be # DSL to Berry transpiler
├── tests/ # Comprehensive test suite
│ ├── test_all.be # Run all tests
│ ├── animation_engine_test.be
│ ├── dsl_transpiler_test.be
│ ├── event_system_test.be
│ └── ... (50+ test files)
├── examples/ # Berry code examples
│ ├── run_all_demos.be # Run all examples
│ ├── simple_engine_test.be # Basic usage
│ ├── color_provider_demo.be # Color system demo
│ ├── event_system_demo.be # Interactive animations
│ └── ... (60+ example files)
└── docs/ # Framework-specific documentation
├── architecture_simplification.md
├── class_hierarchy_reference.md
├── migration_guide.md
└── ... (technical documentation)
```
## DSL Examples (`anim_examples/`)
Ready-to-use animation files in DSL format:
```
anim_examples/
├── aurora_borealis.anim # Northern lights effect
├── breathing_colors.anim # Smooth color breathing
├── fire_demo.anim # Realistic fire simulation
├── palette_demo.anim # Palette showcase
├── rainbow_cycle.anim # Rainbow color cycling
└── simple_pulse.anim # Basic pulsing effect
```
## Compiled Examples (`anim_examples/compiled/`)
Berry code generated from DSL examples (for reference):
```
anim_examples/compiled/
├── aurora_borealis.be # Compiled from aurora_borealis.anim
├── breathing_colors.be # Compiled from breathing_colors.anim
└── ... (compiled versions of .anim files)
```
## Project Specifications (`.kiro/specs/berry-animation-framework/`)
Design documents and specifications:
```
.kiro/specs/berry-animation-framework/
├── design.md # Architecture overview
├── requirements.md # Project requirements (✅ complete)
├── dsl-specification.md # DSL syntax reference
├── dsl-grammar.md # DSL grammar specification
├── EVENT_SYSTEM.md # Event system documentation
├── USER_FUNCTIONS.md # User-defined functions guide
├── palette-quick-reference.md # Palette usage guide
└── future_features.md # Planned enhancements
```
## Archived Documentation (`.docs_archive/`)
Technical implementation documents moved from active documentation:
```
.docs_archive/
├── dsl-transpiler-architecture.md # Detailed transpiler design
├── dsl-implementation.md # Implementation details
├── color_provider_system.md # Color system internals
├── unified-architecture-summary.md # Architecture migration notes
└── ... (50+ archived technical docs)
```
## Key Files for Different Use Cases
### Getting Started
1. **`README.md`** - Project overview and quick examples
2. **`docs/QUICK_START.md`** - 5-minute tutorial
3. **`anim_examples/simple_pulse.anim`** - Basic DSL example
### Learning the API
1. **`docs/API_REFERENCE.md`** - Complete API documentation
2. **`docs/EXAMPLES.md`** - Curated examples with explanations
3. **`lib/libesp32/berry_animation/examples/simple_engine_test.be`** - Basic Berry usage
### Using the DSL
1. **`.kiro/specs/berry-animation-framework/dsl-specification.md`** - Complete DSL syntax
2. **`.kiro/specs/berry-animation-framework/palette-quick-reference.md`** - Palette guide
3. **`anim_examples/`** - Working DSL examples
### Advanced Features
1. **`.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md`** - Custom functions
2. **`.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md`** - Interactive animations
3. **`lib/libesp32/berry_animation/user_functions.be`** - Example custom functions
### Troubleshooting
1. **`docs/TROUBLESHOOTING.md`** - Common issues and solutions
2. **`lib/libesp32/berry_animation/tests/test_all.be`** - Run all tests
3. **`lib/libesp32/berry_animation/examples/run_all_demos.be`** - Test examples
### Framework Development
1. **`.kiro/specs/berry-animation-framework/design.md`** - Architecture overview
2. **`.kiro/specs/berry-animation-framework/requirements.md`** - Project requirements
3. **`lib/libesp32/berry_animation/tests/`** - Test suite for development
## File Naming Conventions
### Source Code
- **`*.be`** - Berry source files
- **`*_test.be`** - Test files
- **`*_demo.be`** - Example/demonstration files
### Documentation
- **`*.md`** - Markdown documentation
- **`README.md`** - Overview documents
- **`QUICK_START.md`** - Getting started guides
- **`API_REFERENCE.md`** - API documentation
### DSL Files
- **`*.anim`** - DSL animation files
- **`*.be`** (in anim_examples/compiled/) - Compiled Berry code from DSL
## Navigation Tips
### For New Users
1. Start with `README.md` for project overview
2. Follow `docs/QUICK_START.md` for hands-on tutorial
3. Browse `anim_examples/` for inspiration
4. Reference `docs/API_REFERENCE.md` when needed
### For DSL Users
1. Learn syntax from `.kiro/specs/berry-animation-framework/dsl-specification.md`
2. Study examples in `anim_examples/`
3. Use palette guide: `.kiro/specs/berry-animation-framework/palette-quick-reference.md`
4. Create custom functions: `.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md`
### For Berry Developers
1. Study `lib/libesp32/berry_animation/examples/simple_engine_test.be`
2. Reference `docs/API_REFERENCE.md` for complete API
3. Run tests: `lib/libesp32/berry_animation/tests/test_all.be`
4. Explore advanced examples in `lib/libesp32/berry_animation/examples/`
### For Framework Contributors
1. Understand architecture: `.kiro/specs/berry-animation-framework/design.md`
2. Review requirements: `.kiro/specs/berry-animation-framework/requirements.md`
3. Study source code in `lib/libesp32/berry_animation/core/`
4. Run comprehensive tests: `lib/libesp32/berry_animation/tests/test_all.be`
This structure provides clear separation between user documentation, source code, examples, and technical specifications, making it easy to find relevant information for any use case.

View File

@ -1,94 +1,45 @@
# Quick Start Guide
Get up and running with the Tasmota Berry Animation Framework in 5 minutes!
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.)
- Basic familiarity with Tasmota console
## Step 1: Basic Setup
## Step 1: Your First Animation
### Import the Framework
```berry
import animation
```
Create a simple pulsing red light:
### Create LED Strip and Engine
```berry
# Create LED strip (adjust count for your setup)
var strip = Leds(30) # 30 LEDs
# Create animation engine
var engine = animation.create_engine(strip)
```
## Step 2: Your First Animation
### Simple Solid Color
```berry
# Create a solid red animation
var red_anim = animation.solid(0xFFFF0000) # ARGB format
# Add to engine and start
engine.add_animation(red_anim)
engine.start()
```
### Pulsing Effect
```berry
# Create pulsing blue animation
var pulse_blue = animation.pulse(
animation.solid(0xFF0000FF), # Blue color
2000, # 2 second period
50, # Min brightness (0-255)
255 # Max brightness (0-255)
)
engine.clear() # Clear previous animations
engine.add_animation(pulse_blue)
engine.start()
```
## Step 3: Using the DSL
The DSL (Domain-Specific Language) makes animations much easier to write.
### Create Animation File
Create `my_first.anim`:
```dsl
# Define colors
color red = #FF0000
color blue = #0000FF
# Create pulsing animation
animation pulse_red = pulse(solid(red), 3s, 20%, 100%)
animation pulse_red = pulsating_animation(color=red, period=3s)
# Run it
run pulse_red
```
### Load DSL Animation
```berry
import animation
## Step 2: Color Cycling
var strip = Leds(30)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
Create smooth color transitions:
# Load from string
var dsl_code = '''
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 2s, 30%, 100%)
run pulse_blue
'''
```dsl
# Use predefined rainbow palette
animation rainbow_cycle = rich_palette(
palette=PALETTE_RAINBOW,
cycle_period=5s,
transition_type=1
)
runtime.load_dsl(dsl_code)
run rainbow_cycle
```
## Step 4: Color Palettes
## Step 3: Custom Palettes
Palettes create smooth color transitions:
Create your own color palettes:
```dsl
# Define a sunset palette
@ -101,23 +52,23 @@ palette sunset = [
]
# Create palette animation
animation sunset_glow = rich_palette_animation(sunset, 5s, smooth, 200)
animation sunset_glow = rich_palette(
palette=sunset,
cycle_period=8s,
transition_type=1
)
run sunset_glow
```
## Step 5: Sequences
## Step 4: Sequences
Create complex shows with sequences:
```dsl
color red = #FF0000
color green = #00FF00
color blue = #0000FF
animation red_pulse = pulse(solid(red), 2s, 50%, 100%)
animation green_pulse = pulse(solid(green), 2s, 50%, 100%)
animation blue_pulse = pulse(solid(blue), 2s, 50%, 100%)
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
@ -125,7 +76,7 @@ sequence rgb_show {
play green_pulse for 3s
wait 500ms
play blue_pulse for 3s
wait 500ms
repeat 2 times:
play red_pulse for 1s
play green_pulse for 1s
@ -135,119 +86,112 @@ sequence rgb_show {
run rgb_show
```
## Step 6: Interactive Animations
## Step 5: Dynamic Effects
Add event handling for interactive effects:
Add movement and variation to your animations:
```dsl
color white = #FFFFFF
color red = #FF0000
# Breathing effect with smooth oscillation
animation breathing = pulsating_animation(
color=blue,
min_brightness=50,
max_brightness=255,
period=4s
)
animation flash_white = solid(white)
animation normal_red = solid(red)
# Moving comet effect
animation comet = comet_animation(
color=white,
tail_length=8,
speed=2000
)
# Flash white when button pressed
on button_press: flash_white
# Sparkle effect
animation sparkles = sparkle_animation(
color=white,
density=80,
fade_speed=60
)
# Main animation
run normal_red
run breathing
```
## Common Patterns
### Fire Effect
```dsl
palette fire = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF8000), # Orange
(255, #FFFF00) # Yellow
]
animation fire = rich_palette(
palette=PALETTE_FIRE,
cycle_period=2s,
transition_type=1
)
animation fire_effect = rich_palette_animation(fire, 2s, smooth, 255)
run fire_effect
run fire
```
### Rainbow Cycle
### Ocean Waves
```dsl
palette rainbow = [
(0, red), (42, orange), (84, yellow),
(126, green), (168, blue), (210, indigo), (255, violet)
]
animation ocean = rich_palette(
palette=PALETTE_OCEAN,
cycle_period=6s,
transition_type=1
)
animation rainbow_cycle = rich_palette_animation(rainbow, 10s, smooth, 255)
run rainbow_cycle
```
### Breathing Effect
```dsl
color soft_blue = #4080FF
animation breathing = pulse(solid(soft_blue), 4s, 10%, 100%)
run breathing
run ocean
```
## Tips for Success
### 1. Start Simple
Begin with solid colors and basic pulses before moving to complex effects.
1. **Start Simple** - Begin with solid colors and basic effects
2. **Use Predefined Palettes** - Try PALETTE_RAINBOW, PALETTE_FIRE, PALETTE_OCEAN
3. **Test Incrementally** - Add one animation at a time
4. **Use Named Colors** - red, blue, green, white, etc.
5. **Start with Longer Periods** - 3-5 seconds, then adjust as needed
### 2. Use the DSL
The DSL is much easier than writing Berry code directly.
## Loading DSL Files
### 3. Test Incrementally
Add one animation at a time and test before adding complexity.
Save your DSL code in `.anim` files and load them:
### 4. Check Your Colors
Use hex color codes (#RRGGBB) or named colors (red, blue, green).
### 5. Mind the Timing
Start with longer periods (3-5 seconds) and adjust as needed.
## Troubleshooting
### Animation Not Starting
```berry
# Make sure to start the engine
engine.start()
import animation
# Check if animation was added
print(engine.size()) # Should be > 0
# Load DSL file
var runtime = animation.load_dsl_file("my_animation.anim")
```
### Colors Look Wrong
```berry
# Check color format (ARGB with alpha channel)
var red = 0xFFFF0000 # Correct: Alpha=FF, Red=FF, Green=00, Blue=00
var red = 0xFF0000 # Wrong: Missing alpha channel
```
## User-Defined Functions
Create custom animation functions in Berry and use them in DSL:
### DSL Compilation Errors
```berry
# Use try/catch for better error messages
try
runtime.load_dsl(dsl_code)
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
# Define custom function - engine must be first parameter
def my_sparkle(engine, color, density, speed)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.density = density
anim.speed = speed
return anim
end
# Register for DSL use
animation.register_user_function("sparkle", my_sparkle)
```
### Performance Issues
```berry
# Limit number of simultaneous animations
engine.clear() # Remove all animations
engine.add_animation(new_animation) # Add just one
# Use longer periods for smoother performance
animation pulse_slow = pulse(solid(red), 5s, 50%, 100%) # 5 seconds instead of 1
```dsl
# Use in DSL - engine is automatically passed
animation gold_sparkles = sparkle(#FFD700, 8, 500ms)
run gold_sparkles
```
**Note**: The DSL automatically passes `engine` as the first argument to user functions.
## Next Steps
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax
- **[API Reference](API_REFERENCE.md)** - Berry API documentation
- **[Examples](EXAMPLES.md)** - More complex examples
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom functions
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Interactive animations
- **[DSL Reference](DSL_REFERENCE.md)** - Complete DSL syntax and features
- **[User Functions](USER_FUNCTIONS.md)** - Create custom animation functions
- **[Examples](EXAMPLES.md)** - More complex animation examples
- **[Animation Class Hierarchy](ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
- **[Oscillation Patterns](OSCILLATION_PATTERNS.md)** - Dynamic value patterns
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
Happy animating! 🎨✨

View File

@ -2,18 +2,19 @@
Common issues and solutions for the Tasmota Berry Animation Framework.
**Note**: This guide focuses on DSL usage, which is the recommended way to create animations. For programmatic API issues, see the [Animation Development Guide](ANIMATION_DEVELOPMENT.md).
## Installation Issues
### Framework Not Found
**Problem:** `import animation` fails with "module not found"
**Problem:** `import animation` or `import animation_dsl` fails with "module not found"
**Solutions:**
1. **Check Module Path:**
1. **Check Module Import:**
```berry
# Verify the animation module exists
import sys
print(sys.path())
import animation # Core framework
import animation_dsl # DSL compiler
```
2. **Set Module Path:**
@ -25,8 +26,9 @@ Common issues and solutions for the Tasmota Berry Animation Framework.
```
lib/libesp32/berry_animation/
├── animation.be # Main module file
├── dsl/ # DSL components
├── core/ # Core classes
├── effects/ # Animation effects
├── animations/ # Animation effects
└── ...
```
@ -56,47 +58,54 @@ Common issues and solutions for the Tasmota Berry Animation Framework.
### Animations Not Starting
**Problem:** Animations created but LEDs don't change
**Problem:** DSL animations compile but LEDs don't change
**Diagnostic Steps:**
```berry
import animation
import animation_dsl
var strip = Leds(30)
var engine = animation.create_engine(strip)
var anim = animation.solid(0xFFFF0000)
# Test basic DSL execution
var dsl_code = "color red = 0xFF0000\n" +
"animation red_anim = solid(color=red)\n" +
"run red_anim"
# Check each step
print("Engine created:", engine != nil)
print("Animation created:", anim != nil)
engine.add_animation(anim)
print("Animation added, count:", engine.size())
engine.start()
print("Engine started:", engine.is_active())
try
animation_dsl.execute(dsl_code)
print("DSL executed successfully")
except .. as e, msg
print("DSL Error:", msg)
end
```
**Common Solutions:**
1. **Forgot to Start Engine:**
```berry
engine.add_animation(anim)
engine.start() # Don't forget this!
1. **Missing Strip Declaration:**
```dsl
# Add explicit strip length if needed
strip length 30
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
```
2. **Animation Not Added:**
```berry
# Make sure animation is added to engine
engine.add_animation(anim)
print("Animation count:", engine.size()) # Should be > 0
2. **Animation Not Executed:**
```dsl
# Make sure you have a 'run' statement
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim # Don't forget this!
```
3. **Strip Not Configured:**
```berry
# Check strip configuration
var strip = Leds(30) # 30 LEDs
print("Strip created:", strip != nil)
3. **Strip Auto-Detection Issues:**
```dsl
# Force strip length if auto-detection fails
strip length 30 # Must be first statement
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
```
### Colors Look Wrong
@ -106,29 +115,36 @@ print("Engine started:", engine.is_active())
**Common Issues:**
1. **Missing Alpha Channel:**
```berry
# Wrong - missing alpha
var red = 0xFF0000
```dsl
# Note: 0xFF0000 is valid RGB format (alpha defaults to 0xFF)
color red = 0xFF0000 # RGB format (alpha=255 assumed)
# Correct - with alpha channel
var red = 0xFFFF0000 # ARGB format
# Explicit alpha channel (ARGB format)
color red = 0xFFFF0000 # ARGB format (alpha=255, red=255)
color semi_red = 0x80FF0000 # ARGB format (alpha=128, red=255)
```
2. **Color Format Confusion:**
```berry
```dsl
# ARGB format: 0xAARRGGBB
var red = 0xFFFF0000 # Alpha=FF, Red=FF, Green=00, Blue=00
var green = 0xFF00FF00 # Alpha=FF, Red=00, Green=FF, Blue=00
var blue = 0xFF0000FF # Alpha=FF, Red=00, Green=00, Blue=FF
color red = 0xFFFF0000 # Alpha=FF, Red=FF, Green=00, Blue=00
color green = 0xFF00FF00 # Alpha=FF, Red=00, Green=FF, Blue=00
color blue = 0xFF0000FF # Alpha=FF, Red=00, Green=00, Blue=FF
```
3. **Brightness Issues:**
```berry
# Check opacity settings
anim.set_opacity(255) # Full brightness
```dsl
# Use opacity parameter or property assignment
animation red_anim = solid(color=red, opacity=255) # Full brightness
# Check pulse brightness ranges
var pulse = animation.pulse(pattern, 2000, 50, 255) # Min=50, Max=255
# Or assign after creation
animation pulse_red = pulsating_animation(color=red, period=2s)
pulse_red.opacity = 200 # Adjust brightness
# Use value providers for dynamic brightness
set brightness = smooth(min_value=50, max_value=255, period=3s)
animation breathing = solid(color=red)
breathing.opacity = brightness
```
### Animations Too Fast/Slow
@ -138,28 +154,35 @@ print("Engine started:", engine.is_active())
**Solutions:**
1. **Check Time Units:**
```berry
# Berry uses milliseconds
var pulse = animation.pulse(pattern, 2000, 50, 255) # 2 seconds
# DSL uses time units
# animation pulse_anim = pulse(pattern, 2s, 20%, 100%) # 2 seconds
```dsl
# DSL uses time units (converted to milliseconds)
animation pulse_anim = pulsating_animation(color=red, period=2s) # 2 seconds
animation fast_pulse = pulsating_animation(color=blue, period=500ms) # 0.5 seconds
```
2. **Adjust Periods:**
```berry
```dsl
# Too fast - increase period
var slow_pulse = animation.pulse(pattern, 5000, 50, 255) # 5 seconds
animation slow_pulse = pulsating_animation(color=red, period=5s) # 5 seconds
# Too slow - decrease period
var fast_pulse = animation.pulse(pattern, 500, 50, 255) # 0.5 seconds
animation fast_pulse = pulsating_animation(color=red, period=500ms) # 0.5 seconds
```
3. **Performance Limitations:**
```berry
# Reduce number of simultaneous animations
engine.clear() # Remove all animations
engine.add_animation(single_animation)
```dsl
# Use sequences instead of multiple simultaneous animations
sequence optimized_show {
play animation1 for 3s
play animation2 for 3s
play animation3 for 3s
}
run optimized_show
# Instead of:
# run animation1
# run animation2
# run animation3
```
## DSL Issues
@ -171,7 +194,7 @@ print("Engine started:", engine.is_active())
**Diagnostic Approach:**
```berry
try
var berry_code = animation.compile_dsl(dsl_source)
var berry_code = animation_dsl.compile(dsl_source)
print("Compilation successful")
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
@ -183,38 +206,63 @@ end
1. **Undefined Colors:**
```dsl
# Wrong - color not defined
animation red_anim = solid(red)
animation red_anim = solid(color=red)
# Correct - define color first
color red = #FF0000
animation red_anim = solid(red)
color red = 0xFF0000
animation red_anim = solid(color=red)
```
2. **Invalid Color Format:**
```dsl
# Wrong - invalid hex format
color red = FF0000
# Correct - with # prefix
# Wrong - # prefix not supported (conflicts with comments)
color red = #FF0000
# Correct - use 0x prefix
color red = 0xFF0000
```
3. **Missing Time Units:**
```dsl
# Wrong - no time unit
animation pulse_anim = pulse(solid(red), 2000, 50%, 100%)
animation pulse_anim = pulsating_animation(color=red, period=2000)
# Correct - with time unit
animation pulse_anim = pulse(solid(red), 2s, 50%, 100%)
animation pulse_anim = pulsating_animation(color=red, period=2s)
```
4. **Reserved Name Conflicts:**
```dsl
# Wrong - 'red' is a predefined color
color red = #800000
color red = 0x800000
# Correct - use different name
color dark_red = #800000
color dark_red = 0x800000
```
5. **Invalid Parameter Names:**
```dsl
# Wrong - invalid parameter name
animation pulse_anim = pulsating_animation(color=red, invalid_param=123)
# Error: "Parameter 'invalid_param' is not valid for pulsating_animation"
# Correct - use valid parameters (see DSL_REFERENCE.md for complete list)
animation pulse_anim = pulsating_animation(color=red, period=2s)
```
6. **Parameter Constraint Violations:**
```dsl
# Wrong - negative period not allowed
animation bad_pulse = pulsating_animation(color=red, period=-2s)
# Error: "Parameter 'period' value -2000 violates constraint: min=1"
# Wrong - invalid enum value
animation bad_comet = comet_animation(color=red, direction=5)
# Error: "Parameter 'direction' value 5 not in allowed values: [-1, 1]"
# Correct - valid parameters within constraints
animation good_pulse = pulsating_animation(color=red, period=2s)
animation good_comet = comet_animation(color=red, direction=1)
```
### DSL Runtime Errors
@ -228,20 +276,40 @@ end
# Add strip declaration if needed
strip length 30
color red = #FF0000
animation red_anim = solid(red)
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
```
2. **Sequence Issues:**
```dsl
# Make sure animations are defined before sequences
color red = #FF0000
animation red_anim = solid(red) # Define first
color red = 0xFF0000
animation red_anim = solid(color=red) # Define first
sequence demo {
play red_anim for 3s # Use after definition
wait 1s # Optional pause between animations
}
run demo
```
3. **Undefined References:**
```dsl
# Wrong - using undefined animation in sequence
sequence bad_demo {
play undefined_animation for 3s
}
# Error: "Undefined reference: 'undefined_animation'"
# Correct - define all references first
color blue = 0x0000FF
animation blue_anim = solid(color=blue)
sequence good_demo {
play blue_anim for 3s
}
run good_demo
```
## Performance Issues
@ -252,37 +320,44 @@ end
**Solutions:**
1. **Reduce Animation Count:**
```berry
# Good - 1-3 animations
engine.clear()
engine.add_animation(main_animation)
1. **Use Sequences Instead of Multiple Animations:**
```dsl
# Good - sequential playback
sequence smooth_show {
play animation1 for 3s
play animation2 for 3s
play animation3 for 3s
}
run smooth_show
# Avoid - too many simultaneous animations
# engine.add_animation(anim1)
# engine.add_animation(anim2)
# ... (10+ animations)
# run animation1
# run animation2
# run animation3
```
2. **Increase Animation Periods:**
```berry
```dsl
# Smooth - longer periods
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
animation smooth_pulse = pulsating_animation(color=red, period=3s)
# Choppy - very short periods
var choppy_pulse = animation.pulse(pattern, 50, 50, 255) # 50ms
animation choppy_pulse = pulsating_animation(color=red, period=50ms)
```
3. **Optimize Value Providers:**
```berry
```dsl
# Efficient - reuse providers
var breathing = animation.smooth(50, 255, 2000)
var anim1 = animation.pulse(pattern1, breathing)
var anim2 = animation.pulse(pattern2, breathing) # Reuse
set breathing = smooth(min_value=50, max_value=255, period=2s)
# Inefficient - create new providers
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000))
color red = 0xFF0000
color blue = 0x0000FF
animation anim1 = pulsating_animation(color=red, period=2s)
anim1.opacity = breathing
animation anim2 = pulsating_animation(color=blue, period=2s)
anim2.opacity = breathing # Reuse same provider
```
### Memory Issues
@ -405,7 +480,15 @@ var strip = Leds(30) # 30 LEDs
strip.set_pixel_color(0, 0xFFFF0000) # Set first pixel red
strip.show() # Update LEDs
# If this doesn't work, check hardware
# Test with animation framework
import animation
var engine = animation.create_engine(strip)
var red_anim = animation.solid(engine)
red_anim.color = 0xFFFF0000
engine.add_animation(red_anim)
engine.start()
# If basic strip works but animation doesn't, check framework setup
```
### Wrong Colors on Hardware
@ -435,20 +518,45 @@ strip.show() # Update LEDs
## Debugging Techniques
### Enable Debug Mode
### DSL vs Berry API Debugging
**For DSL Issues (Recommended):**
```berry
# Enable debug output
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
# Enable DSL debug output
import animation_dsl
# Check generated code
var dsl_code = "color red = 0xFF0000\nanimation test = solid(color=red)\nrun test"
# Check compilation
try
var berry_code = animation.compile_dsl(dsl_source)
var berry_code = animation_dsl.compile(dsl_code)
print("DSL compilation successful")
print("Generated Berry code:")
print(berry_code)
except "dsl_compilation_error" as e, msg
print("Compilation error:", msg)
except .. as e, msg
print("DSL compilation error:", msg)
end
# Execute with debug
try
animation_dsl.execute(dsl_code, true) # debug=true
except .. as e, msg
print("DSL execution error:", msg)
end
```
**For Framework Issues (Advanced):**
```berry
# Direct Berry API debugging (for framework developers)
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip, true) # debug=true
var anim = animation.solid(engine)
anim.color = 0xFFFF0000
engine.add_animation(anim)
engine.start()
```
### Step-by-Step Testing
@ -464,7 +572,8 @@ var engine = animation.create_engine(strip)
print("Engine created:", engine != nil)
print("3. Creating animation...")
var anim = animation.solid(0xFFFF0000)
var anim = animation.solid(engine)
anim.color = 0xFFFF0000
print("Animation created:", anim != nil)
print("4. Adding animation...")
@ -529,8 +638,8 @@ When asking for help, include:
**Code:**
```dsl
color red = #FF0000
animation red_anim = solid(red)
color red = 0xFF0000
animation red_anim = solid(color=red)
run red_anim
```
@ -596,4 +705,54 @@ This format helps identify issues quickly and provide targeted solutions.
- Proper wiring and connections
- Appropriate LED strip for application
## Quick Reference: Common DSL Patterns
### Basic Animation
```dsl
color red = 0xFF0000
animation red_solid = solid(color=red)
run red_solid
```
### Animation with Parameters
```dsl
color blue = 0x0000FF
animation blue_pulse = pulsating_animation(color=blue, period=2s, opacity=200)
run blue_pulse
```
### Using Value Providers
```dsl
set breathing = smooth(min_value=50, max_value=255, period=3s)
color green = 0x00FF00
animation breathing_green = solid(color=green)
breathing_green.opacity = breathing
run breathing_green
```
### Sequences
```dsl
color red = 0xFF0000
color blue = 0x0000FF
animation red_anim = solid(color=red)
animation blue_anim = solid(color=blue)
sequence demo {
play red_anim for 2s
wait 500ms
play blue_anim for 2s
}
run demo
```
### Multiple Strip Lengths
```dsl
strip length 60 # Must be first statement
color rainbow = rainbow_color_provider(period=5s)
animation rainbow_anim = solid(color=rainbow)
run rainbow_anim
```
Following these guidelines will help you avoid most common issues and create reliable LED animations.

View File

@ -0,0 +1,442 @@
# 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
Call your function just like built-in animations:
```dsl
# Use your custom function
animation calm = breathing(blue, 4s)
animation energetic = breathing(red, 1s)
sequence demo {
play calm for 10s
play energetic for 5s
}
run demo
```
## 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)
```
```dsl
animation bright_red = bright(red, 80%)
animation dim_blue = bright(blue, 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)
```
```dsl
animation campfire = fire(200, 2s)
animation torch = fire(255, 500ms)
```
### Sparkle Effects
```berry
def sparkles(engine, color, density, speed)
var anim = animation.twinkle_animation(engine)
anim.color = color
anim.density = density
anim.speed = speed
return anim
end
animation.register_user_function("sparkles", sparkles)
```
```dsl
animation stars = sparkles(white, 12, 300ms)
animation fairy_dust = sparkles(#FFD700, 8, 500ms)
```
### 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)
```
```dsl
animation left_pulse = pulse_at(green, 5, 3, 2s)
animation right_pulse = pulse_at(blue, 25, 3, 2s)
```
## Advanced Examples
### Multi-Layer Effects
```berry
def rainbow_sparkle(engine, base_speed, sparkle_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)
```
### 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)
```
```dsl
animation emergency = strobe()
animation notification = alert()
animation custom_police = police(500ms)
```
## 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 sparkle_effect(engine, color, density, speed)
# ... implementation
end
# Register all functions
animation.register_user_function("breathing", breathing)
animation.register_user_function("fire", fire_effect)
animation.register_user_function("sparkle", sparkle_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
```
## 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
var dsl_code = '''
animation my_fire = fire(200, 1500ms)
animation my_sparkles = sparkle(white, 8, 400ms)
sequence show {
play my_fire for 10s
play my_sparkles for 5s
}
run show
'''
animation_dsl.execute(dsl_code)
```
### From Files
```berry
# Save DSL with custom functions
var my_show = '''
animation campfire = fire(180, 2s)
animation stars = sparkle(#FFFFFF, 6, 600ms)
sequence night_scene {
play campfire for 30s
play stars for 10s
}
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:
```dsl
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.

View File

@ -1,58 +0,0 @@
# Changelog
All notable changes to the Berry Animation Framework will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.1.0] - 2025-07-31
### Added
- Initial public release of the Berry Animation Framework
- Numeric version system (0xAABBCCDD format) with string conversion function
- Core animation engine with unified architecture
- 21 built-in animation effects including:
- Basic animations (pulse, breathe, filled, comet, twinkle)
- Position-based animations (pulse_position, crenel_position)
- Advanced pattern animations (noise, plasma, sparkle, wave)
- Motion effect animations (shift, bounce, scale, jitter)
- Fire and gradient effects
- Comprehensive DSL (Domain Specific Language) system
- Value provider system for dynamic parameters
- Color provider system with palette support
- Event system for animation coordination
- Sequence manager for complex animation sequences
- 52 comprehensive test files with 100% pass rate
- 60+ example and demo files
- Complete API documentation
- Performance optimizations for embedded systems
- MIT License
### Features
- **157 Berry files** with modular architecture
- **Integer-only arithmetic** for embedded performance
- **Memory-efficient** frame buffer management
- **Extensible plugin system** for custom animations
- **Fast loop integration** with Tasmota
- **Parameter validation** and runtime safety
- **Comprehensive error handling** with proper exceptions
- **Cross-platform compatibility** (Tasmota/Berry environments)
### Documentation
- Complete API reference with examples
- Advanced patterns implementation guide
- Motion effects documentation
- DSL syntax reference and grammar specification
- Value provider system documentation
- Performance tips and troubleshooting guides
- Migration guides and architecture documentation
### Testing
- 52 test files covering all components
- Unit tests for individual animations
- Integration tests for engine and DSL
- Performance and stress tests
- Error handling and edge case tests
- 100% test pass rate
[0.1.0]: https://github.com/your-repo/berry-animation-framework/releases/tag/v0.1.0

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Berry Animation Framework Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,215 +0,0 @@
# Tasmota Berry Animation Framework
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using Berry scripting language.
## ✨ Features
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, twinkle, and more
- **🌈 Advanced Color System** - Palettes, gradients, color cycling with smooth transitions
- **📝 Domain-Specific Language (DSL)** - Write animations in intuitive, declarative syntax
- **⚡ High Performance** - Optimized for embedded systems with minimal memory usage
- **🔧 Extensible** - Create custom animations and user-defined functions
- **🎯 Position-Based Effects** - Precise control over individual LED positions
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with value providers
- **🎭 Event System** - Responsive animations that react to button presses, timers, and sensors
## 🚀 Quick Start
### 1. Basic Berry Animation
```berry
import animation
# Create LED strip (60 LEDs)
var strip = Leds(60)
var engine = animation.create_engine(strip)
# Create a pulsing red animation
var pulse_red = animation.pulse(
animation.solid(0xFFFF0000), # Red color
2000, # 2 second period
50, # Min brightness (0-255)
255 # Max brightness (0-255)
)
# Start the animation
engine.add_animation(pulse_red)
engine.start()
```
### 2. Using the Animation DSL
Create a file `my_animation.anim`:
```dsl
# Define colors
color red = #FF0000
color blue = #0000FF
# Create animations
animation pulse_red = pulse(solid(red), 2s, 20%, 100%)
animation pulse_blue = pulse(solid(blue), 3s, 30%, 100%)
# Create a sequence
sequence demo {
play pulse_red for 5s
wait 1s
play pulse_blue for 5s
repeat 3 times:
play pulse_red for 2s
play pulse_blue for 2s
}
run demo
```
Load and run the DSL:
```berry
import animation
var strip = Leds(60)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
runtime.load_dsl_file("my_animation.anim")
```
### 3. Palette-Based Animations
```dsl
# Define a fire palette
palette fire_colors = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF8000), # Orange
(255, #FFFF00) # Yellow
]
# Create fire animation
animation fire_effect = rich_palette_animation(fire_colors, 3s, smooth, 255)
run fire_effect
```
## 📚 Documentation
### User Guides
- **[Quick Start Guide](docs/QUICK_START.md)** - Get up and running in 5 minutes
- **[API Reference](docs/API_REFERENCE.md)** - Complete Berry API documentation
- **[Examples](docs/EXAMPLES.md)** - Curated examples with explanations
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions
### DSL (Domain-Specific Language)
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax guide
- **[DSL Grammar](.kiro/specs/berry-animation-framework/dsl-grammar.md)** - Formal grammar specification
- **[Palette Guide](.kiro/specs/berry-animation-framework/palette-quick-reference.md)** - Working with color palettes
### Advanced Topics
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom animation functions
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Responsive, interactive animations
- **[Project Structure](docs/PROJECT_STRUCTURE.md)** - Navigate the codebase
### Framework Design
- **[Requirements](.kiro/specs/berry-animation-framework/requirements.md)** - Project goals and requirements (✅ Complete)
- **[Architecture](.kiro/specs/berry-animation-framework/design.md)** - Framework design and architecture
- **[Future Features](.kiro/specs/berry-animation-framework/future_features.md)** - Planned enhancements
## 🎯 Core Concepts
### Unified Architecture
The framework uses a **unified pattern-animation architecture** where `Animation` extends `Pattern`. This means:
- Animations ARE patterns with temporal behavior
- Infinite composition: animations can use other animations as base patterns
- Consistent API across all visual elements
### Animation Engine
The `AnimationEngine` is the heart of the framework:
- Manages multiple animations with priority-based layering
- Handles timing, blending, and LED output
- Integrates with Tasmota's `fast_loop` for smooth performance
### Value Providers
Dynamic parameters that change over time:
- **Static values**: `solid(red)`
- **Oscillators**: `pulse(solid(red), smooth(50, 255, 2s))`
- **Color providers**: `rich_palette_animation(fire_palette, 3s)`
## 🎨 Animation Types
### Basic Animations
- **`solid(color)`** - Static color fill
- **`pulse(pattern, period, min, max)`** - Pulsing brightness
- **`breathe(color, period)`** - Smooth breathing effect
### Pattern-Based Animations
- **`rich_palette_animation(palette, period, easing, brightness)`** - Palette color cycling
- **`gradient(color1, color2, ...)`** - Color gradients
- **`fire_animation(intensity, speed)`** - Realistic fire simulation
### Position-Based Animations
- **`pulse_position_animation(color, pos, size, fade)`** - Localized pulse
- **`comet_animation(color, tail_length, speed)`** - Moving comet effect
- **`twinkle_animation(color, density, speed)`** - Twinkling stars
### Motion Effects
- **`shift_left(pattern, speed)`** - Move pattern left
- **`shift_right(pattern, speed)`** - Move pattern right
- **`bounce(pattern, period)`** - Bouncing motion
## 🔧 Installation
### For Tasmota Development
1. Copy the `lib/libesp32/berry_animation/` directory to your Tasmota build
2. The framework will be available as the `animation` module
3. Use `import animation` in your Berry scripts
### For Testing/Development
1. Install Berry interpreter with Tasmota extensions
2. Set module path: `berry -m lib/libesp32/berry_animation`
3. Run examples: `berry examples/simple_engine_test.be`
## 🧪 Examples
The framework includes comprehensive examples:
### Berry Examples
- **[Basic Engine](lib/libesp32/berry_animation/examples/simple_engine_test.be)** - Simple animation setup
- **[Color Providers](lib/libesp32/berry_animation/examples/color_provider_demo.be)** - Dynamic color effects
- **[Position Effects](lib/libesp32/berry_animation/examples/pulse_position_animation_demo.be)** - Localized animations
- **[Event System](lib/libesp32/berry_animation/examples/event_system_demo.be)** - Interactive animations
### DSL Examples
- **[Aurora Borealis](anim_examples/aurora_borealis.anim)** - Northern lights effect
- **[Breathing Colors](anim_examples/breathing_colors.anim)** - Smooth color breathing
- **[Fire Effect](anim_examples/fire_demo.anim)** - Realistic fire simulation
## 🤝 Contributing
### Running Tests
```bash
# Run all tests
berry lib/libesp32/berry_animation/tests/test_all.be
# Run specific test
berry lib/libesp32/berry_animation/tests/animation_engine_test.be
```
### Code Style
- Follow Berry language conventions
- Use descriptive variable names
- Include comprehensive comments
- Add test coverage for new features
## 📄 License
This project is part of the Tasmota ecosystem and follows the same licensing terms.
## 🙏 Acknowledgments
- **Tasmota Team** - For the excellent IoT platform
- **Berry Language** - For the lightweight scripting language
- **Community Contributors** - For testing, feedback, and improvements
---
**Ready to create amazing LED animations?** Start with the [Quick Start Guide](docs/QUICK_START.md)!

View File

@ -4,7 +4,6 @@
# into a unified "animation" object for use in Tasmota LED strip control.
#
# The framework provides:
# - Unified Pattern-Animation architecture (Animation extends Pattern)
# - DSL (Domain Specific Language) for declarative animation definitions
# - Value providers for dynamic parameters (oscillators, color providers)
# - Event system for interactive animations
@ -58,15 +57,15 @@ end
# Import core framework components
# These provide the fundamental architecture for the animation system
# Base class for parameter management - shared by Animation and ValueProvider
import "core/parameterized_object" as parameterized_object
register_to_animation(parameterized_object)
# Frame buffer management for LED strip pixel data
import "core/frame_buffer" as frame_buffer
register_to_animation(frame_buffer)
# Base Pattern class - foundation for all visual elements
import "core/pattern_base" as pattern_base
register_to_animation(pattern_base)
# Base Animation class - extends Pattern with temporal behavior
# Base Animation class - unified foundation for all visual elements
import "core/animation_base" as animation_base
register_to_animation(animation_base)
@ -87,57 +86,6 @@ register_to_animation(event_handler)
import "core/user_functions" as user_functions
register_to_animation(user_functions)
# Import effects
import "effects/filled" as filled_animation
register_to_animation(filled_animation)
import "effects/pulse" as pulse_animation
register_to_animation(pulse_animation)
import "effects/pulse_position" as pulse_position_animation
register_to_animation(pulse_position_animation)
import "effects/crenel_position" as crenel_position_animation
register_to_animation(crenel_position_animation)
import "effects/breathe" as breathe_animation
register_to_animation(breathe_animation)
import "effects/palette_pattern" as palette_pattern_animation
register_to_animation(palette_pattern_animation)
import "effects/comet" as comet_animation
register_to_animation(comet_animation)
import "effects/fire" as fire_animation
register_to_animation(fire_animation)
import "effects/twinkle" as twinkle_animation
register_to_animation(twinkle_animation)
import "effects/gradient" as gradient_animation
register_to_animation(gradient_animation)
import "effects/noise" as noise_animation
register_to_animation(noise_animation)
import "effects/plasma" as plasma_animation
register_to_animation(plasma_animation)
import "effects/sparkle" as sparkle_animation
register_to_animation(sparkle_animation)
import "effects/wave" as wave_animation
register_to_animation(wave_animation)
import "effects/shift" as shift_animation
register_to_animation(shift_animation)
import "effects/bounce" as bounce_animation
register_to_animation(bounce_animation)
import "effects/scale" as scale_animation
register_to_animation(scale_animation)
import "effects/jitter" as jitter_animation
register_to_animation(jitter_animation)
# Import palette examples
import "effects/palettes" as palettes
register_to_animation(palettes)
# Import pattern implementations
import "patterns/solid_pattern" as solid_pattern_impl
register_to_animation(solid_pattern_impl)
# Import animation implementations
# Note: pulse_animation is already imported from effects/pulse.be
import "effects/pattern_animation" as pattern_animation_impl
register_to_animation(pattern_animation_impl)
# Import value providers
import "providers/value_provider.be" as value_provider
register_to_animation(value_provider)
@ -153,20 +101,58 @@ import "providers/color_cycle_color_provider.be" as color_cycle_color_provider
register_to_animation(color_cycle_color_provider)
import "providers/composite_color_provider.be" as composite_color_provider
register_to_animation(composite_color_provider)
import "providers/solid_color_provider.be" as solid_color_provider
register_to_animation(solid_color_provider)
import "providers/static_color_provider.be" as static_color_provider
register_to_animation(static_color_provider)
import "providers/rich_palette_color_provider.be" as rich_palette_color_provider
register_to_animation(rich_palette_color_provider)
import "providers/breathe_color_provider.be" as breathe_color_provider
register_to_animation(breathe_color_provider)
# Import DSL components
import "dsl/token.be" as dsl_token
register_to_animation(dsl_token)
import "dsl/lexer.be" as dsl_lexer
register_to_animation(dsl_lexer)
import "dsl/transpiler.be" as dsl_transpiler
register_to_animation(dsl_transpiler)
import "dsl/runtime.be" as dsl_runtime
register_to_animation(dsl_runtime)
# Import animations
import "animations/solid" as solid_impl
register_to_animation(solid_impl)
import "animations/beacon" as beacon_animation
register_to_animation(beacon_animation)
import "animations/crenel_position" as crenel_position_animation
register_to_animation(crenel_position_animation)
import "animations/breathe" as breathe_animation
register_to_animation(breathe_animation)
import "animations/palette_pattern" as palette_pattern_animation
register_to_animation(palette_pattern_animation)
import "animations/comet" as comet_animation
register_to_animation(comet_animation)
import "animations/fire" as fire_animation
register_to_animation(fire_animation)
import "animations/twinkle" as twinkle_animation
register_to_animation(twinkle_animation)
import "animations/gradient" as gradient_animation
register_to_animation(gradient_animation)
import "animations/noise" as noise_animation
register_to_animation(noise_animation)
import "animations/plasma" as plasma_animation
register_to_animation(plasma_animation)
import "animations/sparkle" as sparkle_animation
register_to_animation(sparkle_animation)
import "animations/wave" as wave_animation
register_to_animation(wave_animation)
import "animations/shift" as shift_animation
register_to_animation(shift_animation)
import "animations/bounce" as bounce_animation
register_to_animation(bounce_animation)
import "animations/scale" as scale_animation
register_to_animation(scale_animation)
import "animations/jitter" as jitter_animation
register_to_animation(jitter_animation)
# Import palette examples
import "animations/palettes" as palettes
register_to_animation(palettes)
# Import specialized animation classes
import "animations/rich_palette_animation" as rich_palette_animation
register_to_animation(rich_palette_animation)
# DSL components are now in separate animation_dsl module
# Function called to initialize the `Leds` and `engine` objects
#
@ -185,7 +171,7 @@ end
animation.init_strip = animation_init_strip
# Global variable resolver with error checking
# Used by DSL transpiler to resolve variable names during compilation
# Used by DSL-generated code to resolve variable names during execution
# First checks animation module, then global scope for user-defined variables
def animation_global(name, module_name)
import global

View File

@ -0,0 +1,98 @@
# Berry Animation Framework - DSL Module
#
# This module provides Domain-Specific Language (DSL) functionality for the
# Berry Animation Framework. It allows users to write animations using a
# declarative syntax that gets transpiled to Berry code.
#
# The DSL provides:
# - Declarative animation definitions with intuitive syntax
# - Color and palette definitions
# - Animation sequences and timing control
# - Property assignments and dynamic parameters
# - Event system integration
# - User-defined functions
#
# Usage:
# import animation_dsl
# var berry_code = animation_dsl.compile(dsl_source)
# animation_dsl.execute(berry_code)
#
import global
# Requires to first `import animation`
# We don't include it to not create a closure, but use the global instead
# Create the DSL module and make it globally accessible
#@ solidify:animation_dsl,weak
var animation_dsl = module("animation_dsl")
global.animation_dsl = animation_dsl
# Version information for compatibility tracking
animation_dsl.VERSION = animation.VERSION
# Helper function to register all exports from imported modules into the DSL module
def register_to_dsl(m)
for k: m.keys()
animation_dsl.(k) = m[k]
end
end
# Import DSL components
import "dsl/token.be" as dsl_token
register_to_dsl(dsl_token)
import "dsl/lexer.be" as dsl_lexer
register_to_dsl(dsl_lexer)
import "dsl/transpiler.be" as dsl_transpiler
register_to_dsl(dsl_transpiler)
import "dsl/runtime.be" as dsl_runtime
register_to_dsl(dsl_runtime)
# Main DSL compilation function
# Compiles DSL source code to Berry code
#
# @param source: string - DSL source code
# @return string - Generated Berry code
def compile_dsl_source(source)
return animation_dsl.compile_dsl(source)
end
animation_dsl.compile = compile_dsl_source
# Execute DSL source code
# Compiles and executes DSL source in one step
#
# @param source: string - DSL source code
# @return any - Result of execution
def execute(source)
var berry_code = animation_dsl.compile(source)
var compiled_fn = compile(berry_code)
return compiled_fn()
end
animation_dsl.execute = execute
# Load and execute DSL from file
#
# @param filename: string - Path to DSL file
# @return any - Result of execution
def load_file(filename)
var f = open(filename, "r")
if f == nil
raise "io_error", f"Cannot open DSL file: {filename}"
end
var source = f.read()
f.close()
return animation_dsl.execute(source)
end
animation_dsl.load_file = load_file
# Create a DSL runtime instance
#
# @return DSLRuntime - New runtime instance
def create_runtime(strip, debug_mode)
var engine = animation.create_engine(strip)
return animation_dsl.DSLRuntime(engine, debug_mode)
end
animation_dsl.create_runtime = create_runtime
return animation_dsl

View File

@ -0,0 +1,151 @@
# Beacon animation effect for Berry Animation Framework
#
# This animation creates a beacon effect at a specific position on the LED strip.
# It displays a color beacon with optional slew (fade) regions on both sides.
#
# Beacon diagram:
# pos (1)
# |
# v
# _______
# / \
# _______/ \____________
# | | | |
# |2| 3 |2|
#
# 1: `pos`, start of the beacon (in pixel)
# 2: `slew_size`, number of pixels to fade from back to fore color, can be `0`
# 3: `beacon_size`, number of pixels of the beacon
#@ 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}
}
# Render the beacon to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if frame == nil
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
var pixel_size = frame.width
# 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 beacon boundaries
var beacon_min = pos
var beacon_max = pos + beacon_size
# Clamp to frame boundaries
if beacon_min < 0
beacon_min = 0
end
if beacon_max >= pixel_size
beacon_max = pixel_size
end
# Draw the main beacon
var i = beacon_min
while i < beacon_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 beacon 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 (255 = background, 0 = beacon color)
var blend_factor
if slew_size == 1
# For single pixel slew, use 50% blend
blend_factor = 128
else
blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0)
end
# Create color with appropriate alpha for blending
var alpha = 255 - blend_factor # Invert so 0 = transparent, 255 = opaque
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 beacon 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 (0 = beacon color, 255 = background)
var blend_factor
if slew_size == 1
# For single pixel slew, use 50% blend
blend_factor = 128
else
blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255)
end
# Create color with appropriate alpha for blending
var alpha = 255 - blend_factor # Start opaque, fade to transparent
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
# 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}

View File

@ -0,0 +1,287 @@
# Bounce animation effect for Berry Animation Framework
#
# This animation creates bouncing effects where patterns bounce back and forth
# across the LED strip with configurable physics and damping.
#@ solidify:BounceAnimation,weak
class BounceAnimation : animation.animation
# Non-parameter instance variables only
var current_position # Current position in 1/256th pixels
var current_velocity # Current velocity in 1/256th pixels per second
var bounce_center # Center point for bouncing
var source_frame # Frame buffer for source animation
var current_colors # Array of current colors for each pixel
var last_update_time # Last update time for physics calculation
# Parameter definitions following parameterized class specification
static var PARAMS = {
"source_animation": {"type": "instance", "default": nil},
"bounce_speed": {"min": 0, "max": 255, "default": 128},
"bounce_range": {"min": 0, "max": 1000, "default": 0},
"damping": {"min": 0, "max": 255, "default": 250},
"gravity": {"min": 0, "max": 255, "default": 0}
}
# Initialize a new Bounce animation
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize non-parameter instance variables only
self.current_position = 0
self.current_velocity = 0
self.bounce_center = 0
self.source_frame = nil
self.current_colors = []
self.last_update_time = 0
# Initialize with default strip length
self._initialize_buffers()
end
# Initialize frame buffers and arrays
def _initialize_buffers()
var current_strip_length = self.engine.get_strip_length()
self.bounce_center = current_strip_length * 256 / 2 # Center in 1/256th pixels
self.current_position = self.bounce_center
# Initialize velocity based on bounce_speed
var pixels_per_second = tasmota.scale_uint(self.bounce_speed, 0, 255, 0, 20)
self.current_velocity = pixels_per_second * 256 # Convert to 1/256th pixels per second
# Initialize rendering buffers
self.source_frame = animation.frame_buffer(current_strip_length)
self.current_colors.resize(current_strip_length)
# Initialize colors to black
var i = 0
while i < current_strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Override start method for timing control and ValueProvider propagation
def start(time_ms)
# Call parent start first (handles ValueProvider propagation)
super(self).start(time_ms)
# Reset physics state for fresh start/restart
var actual_start_time = time_ms != nil ? time_ms : self.engine.time_ms
self.last_update_time = actual_start_time
# Reset position and velocity
self._initialize_buffers()
# Start source animation if it exists
var current_source = self.source_animation
if current_source != nil
current_source.start(actual_start_time)
end
return self
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "bounce_speed"
# Update velocity if speed changed
var pixels_per_second = tasmota.scale_uint(value, 0, 255, 0, 20)
var new_velocity = pixels_per_second * 256
# Preserve direction
if self.current_velocity < 0
self.current_velocity = -new_velocity
else
self.current_velocity = new_velocity
end
end
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Initialize last_update_time on first update
if self.last_update_time == 0
self.last_update_time = time_ms
end
# Calculate time delta
var dt = time_ms - self.last_update_time
if dt <= 0
return true
end
self.last_update_time = time_ms
# Update physics
self._update_physics(dt)
# Update source animation if it exists
var current_source = self.source_animation
if current_source != nil
if !current_source.is_running
current_source.start(self.start_time)
end
current_source.update(time_ms)
end
# Calculate bounced colors
self._calculate_bounce()
return true
end
# Update bounce physics
def _update_physics(dt_ms)
# Cache parameter values for performance
var current_gravity = self.gravity
var current_bounce_range = self.bounce_range
var current_strip_length = self.engine.get_strip_length()
var current_damping = self.damping
# Use integer arithmetic for physics (dt in milliseconds)
# Apply gravity (downward acceleration)
if current_gravity > 0
var gravity_accel = tasmota.scale_uint(current_gravity, 0, 255, 0, 1000) # pixels/sec²
# Convert to 1/256th pixels per millisecond: accel * dt / 1000
var velocity_change = gravity_accel * dt_ms / 1000
self.current_velocity += velocity_change
end
# Update position: velocity is in 1/256th pixels per second
# Convert to position change: velocity * dt / 1000
self.current_position += self.current_velocity * dt_ms / 1000
# Calculate bounce boundaries
var effective_range = current_bounce_range > 0 ? current_bounce_range : current_strip_length
var half_range = effective_range * 256 / 2
var min_pos = self.bounce_center - half_range
var max_pos = self.bounce_center + half_range
# Check for bounces
var bounced = false
if self.current_position <= min_pos
self.current_position = min_pos
self.current_velocity = -self.current_velocity
bounced = true
elif self.current_position >= max_pos
self.current_position = max_pos
self.current_velocity = -self.current_velocity
bounced = true
end
# Apply damping on bounce
if bounced && current_damping < 255
var damping_factor = tasmota.scale_uint(current_damping, 0, 255, 0, 255)
self.current_velocity = tasmota.scale_uint(self.current_velocity, 0, 255, 0, damping_factor)
if self.current_velocity < 0
self.current_velocity = -tasmota.scale_uint(-self.current_velocity, 0, 255, 0, damping_factor)
end
end
end
# Calculate bounced colors for all pixels
def _calculate_bounce()
# Clear source frame
self.source_frame.clear()
# Render source animation to frame
var current_source = self.source_animation
if current_source != nil
current_source.render(self.source_frame, 0)
end
# Cache strip length for performance
var current_strip_length = self.engine.get_strip_length()
# Apply bounce transformation
var pixel_position = self.current_position / 256 # Convert to pixel units
var offset = pixel_position - current_strip_length / 2 # Offset from center
var i = 0
while i < current_strip_length
var source_pos = i - offset
# Clamp to strip bounds
if source_pos >= 0 && source_pos < current_strip_length
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
else
self.current_colors[i] = 0xFF000000 # Black for out-of-bounds
end
i += 1
end
end
# Render bounce to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var current_strip_length = self.engine.get_strip_length()
var i = 0
while i < current_strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
return f"BounceAnimation(speed={self.bounce_speed}, damping={self.damping}, gravity={self.gravity}, priority={self.priority}, running={self.is_running})"
end
end
# Factory functions following parameterized class specification
# Create a basic bounce animation
#
# @param engine: AnimationEngine - Animation engine instance
# @return BounceAnimation - A new bounce animation instance
def bounce_basic(engine)
var bounce = animation.bounce_animation(engine)
bounce.bounce_speed = 128
bounce.bounce_range = 0 # full strip range
bounce.damping = 250
bounce.gravity = 0
bounce.name = "bounce_basic"
return bounce
end
# Create a gravity bounce animation
#
# @param engine: AnimationEngine - Animation engine instance
# @return BounceAnimation - A new bounce animation instance
def bounce_gravity(engine)
var bounce = animation.bounce_animation(engine)
bounce.bounce_speed = 100
bounce.bounce_range = 0 # full strip range
bounce.damping = 240
bounce.gravity = 128
bounce.name = "bounce_gravity"
return bounce
end
# Create a constrained bounce animation
#
# @param engine: AnimationEngine - Animation engine instance
# @return BounceAnimation - A new bounce animation instance
def bounce_constrained(engine)
var bounce = animation.bounce_animation(engine)
bounce.bounce_speed = 150
bounce.bounce_range = 15 # constrained range
bounce.damping = 250
bounce.gravity = 0
bounce.name = "bounce_constrained"
return bounce
end
return {'bounce_animation': BounceAnimation, 'bounce_basic': bounce_basic, 'bounce_gravity': bounce_gravity, 'bounce_constrained': bounce_constrained}

View File

@ -0,0 +1,96 @@
# Breathe animation effect for Berry Animation Framework
#
# This animation creates a breathing/pulsing effect that oscillates between a minimum and maximum brightness.
# It supports different curve patterns from simple sine waves to natural breathing with pauses.
# It's useful for creating both smooth pulsing effects and calming, organic lighting effects.
#
# The effect uses a breathe_color_provider internally to generate the breathing color effect.
# - curve_factor 1: Pure cosine wave (equivalent to pulse animation)
# - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses)
#@ solidify:BreatheAnimation,weak
class BreatheAnimation : animation.animation
# Non-parameter instance variables only
var breathe_provider # Internal breathe color provider
# Parameter definitions following parameterized class specification
static var 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
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
}
# Initialize a new Breathe animation
# Following parameterized class specification - engine parameter only
#
# @param engine: AnimationEngine - The animation engine (required)
def init(engine)
# Call parent constructor with engine parameter only
super(self).init(engine)
# Create internal breathe color provider
self.breathe_provider = animation.breathe_color(engine)
# Set the animation's color parameter to use the breathe provider
self.color = self.breathe_provider
end
# Handle parameter changes - propagate to internal breathe provider
def on_param_changed(name, value)
# Propagate relevant parameters to the breathe provider
if name == "base_color"
self.breathe_provider.base_color = value
elif name == "min_brightness"
self.breathe_provider.min_brightness = value
elif name == "max_brightness"
self.breathe_provider.max_brightness = value
elif name == "period"
self.breathe_provider.duration = value
elif name == "curve_factor"
self.breathe_provider.curve_factor = value
end
end
# Override start method to synchronize the internal provider
#
# @param start_time: int - Optional start time in milliseconds
# @return self for method chaining
def start(start_time)
# 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)
return self
end
# The render method is inherited from Animation base class
# It automatically uses self.color (which is set to self.breathe_provider)
# The breathe_provider produces the breathing color effect
# 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})"
end
end
# Factory method to create a pulsating animation (sine wave, equivalent to old pulse.be)
def pulsating_animation(engine)
var anim = animation.breathe_animation(engine)
anim.curve_factor = 1 # Pure sine wave for pulsing effect
anim.period = 1000 # Faster default period for pulsing
return anim
end
return {'breathe_animation': BreatheAnimation, 'pulsating_animation': pulsating_animation}

View File

@ -0,0 +1,195 @@
# Comet animation effect for Berry Animation Framework
#
# This animation creates a comet effect with a bright head and a fading tail.
# The comet moves across the LED strip with customizable speed, length, and direction.
#
# The comet uses sub-pixel positioning (1/256th pixels) for smooth movement and supports
# both wrapping around the strip and bouncing off the ends.
#@ solidify:CometAnimation,weak
class CometAnimation : animation.animation
# Non-parameter instance variables only
var head_position # Current position of the comet head (in 1/256th pixels for smooth movement)
# Parameter definitions following parameterized class specification
static var PARAMS = {
"color": {"default": 0xFFFFFFFF}, # Color for the comet head (32-bit ARGB value)
"tail_length": {"min": 1, "max": 50, "default": 5}, # Length of the comet tail in pixels
"speed": {"min": 1, "max": 25600, "default": 2560}, # Movement speed in 1/256th pixels per second
"direction": {"enum": [-1, 1], "default": 1}, # Direction of movement (1 = forward, -1 = backward)
"wrap_around": {"min": 0, "max": 1, "default": 1}, # Whether comet wraps around the strip (bool)
"fade_factor": {"min": 0, "max": 255, "default": 179} # How quickly the tail fades (0-255, 255 = no fade)
}
# Initialize a new Comet animation
# Following parameterized class specification - engine parameter only
#
# @param engine: AnimationEngine - The animation engine (required)
def init(engine)
# Call parent constructor with engine parameter only
super(self).init(engine)
# Initialize non-parameter instance variables only
# Initialize position based on default direction (forward = start at beginning)
self.head_position = 0
end
# Handle parameter changes - reset position when direction changes
def on_param_changed(name, value)
if name == "direction"
# Reset position when direction changes
var strip_length = self.engine.get_strip_length()
if value > 0
self.head_position = 0 # Start at beginning for forward movement
else
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
end
end
end
# Update animation state based on current time
#
# @param time_ms: int - current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
# Call parent update method first
if !super(self).update(time_ms)
return false
end
# Cache parameter values for performance (read once, use multiple times)
var current_speed = self.speed
var current_direction = self.direction
var current_wrap_around = self.wrap_around
var strip_length = self.engine.get_strip_length()
# Calculate elapsed time since animation started
var elapsed = time_ms - self.start_time
# Calculate movement based on elapsed time and speed
# speed is in 1/256th pixels per second, elapsed is in milliseconds
# distance = (speed * elapsed_ms) / 1000
var distance_moved = (current_speed * elapsed * current_direction) / 1000
# Update head position
if current_direction > 0
self.head_position = distance_moved
else
self.head_position = ((strip_length - 1) * 256) + distance_moved
end
# Handle wrapping or bouncing (convert to pixel boundaries)
var strip_length_subpixels = strip_length * 256
if current_wrap_around != 0
# Wrap around the strip
while self.head_position >= strip_length_subpixels
self.head_position -= strip_length_subpixels
end
while self.head_position < 0
self.head_position += strip_length_subpixels
end
else
# Bounce off the ends
if self.head_position >= strip_length_subpixels
self.head_position = (strip_length - 1) * 256
# Update direction parameter using virtual member assignment
self.direction = -current_direction
elif self.head_position < 0
self.head_position = 0
# Update direction parameter using virtual member assignment
self.direction = -current_direction
end
end
return true
end
# Render the comet to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Current time in milliseconds
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Get the integer position of the head (convert from 1/256th pixels to pixels)
var head_pixel = self.head_position / 256
# Get current parameter values using virtual member access (resolves ValueProviders automatically)
var current_color = self.color
var tail_length = self.tail_length
var direction = self.direction
var wrap_around = self.wrap_around
var fade_factor = self.fade_factor
var strip_length = self.engine.get_strip_length()
# Extract color components from current color (ARGB format)
var head_a = (current_color >> 24) & 0xFF
var head_r = (current_color >> 16) & 0xFF
var head_g = (current_color >> 8) & 0xFF
var head_b = current_color & 0xFF
# Render the comet head and tail
var i = 0
while i < tail_length
var pixel_pos = head_pixel - (i * direction)
# Handle wrapping for pixel position
if wrap_around != 0
while pixel_pos >= strip_length
pixel_pos -= strip_length
end
while pixel_pos < 0
pixel_pos += strip_length
end
else
# Skip pixels outside the strip
if pixel_pos < 0 || pixel_pos >= strip_length
i += 1
continue
end
end
# Calculate alpha based on distance from head (alpha-based fading)
var alpha = 255 # Start at full alpha for head
if i > 0
# Use fade_factor to calculate exponential alpha decay
var j = 0
while j < i
alpha = tasmota.scale_uint(alpha, 0, 255, 0, fade_factor)
j += 1
end
end
# Keep RGB components at full brightness, only fade via alpha
# This creates a more realistic comet tail that fades to transparent
var pixel_color = (alpha << 24) | (head_r << 16) | (head_g << 8) | head_b
# Set the pixel in the frame buffer
if pixel_pos >= 0 && pixel_pos < frame.width
frame.set_pixel_color(pixel_pos, pixel_color)
end
i += 1
end
return true
end
# String representation of the animation
def tostring()
var color_str
if animation.is_value_provider(self.color)
color_str = str(self.color)
else
color_str = f"0x{self.color :08x}"
end
return f"CometAnimation(color={color_str}, head_pos={self.head_position / 256:.1f}, tail_length={self.tail_length}, speed={self.speed}, direction={self.direction}, priority={self.priority}, running={self.is_running})"
end
end
return {'comet_animation': CometAnimation}

View File

@ -0,0 +1,132 @@
# Crenel Position animation effect for Berry Animation Framework
#
# This animation creates a crenel (square wave) effect at a specific position on the LED strip.
# It displays repeating rectangular pulses with configurable spacing and count.
#
# Crenel diagram:
# pos (1)
# |
# v (*4)
# ______ ____
# | | |
# _________| |_________|
#
# | 2 | 3 |
#
# 1: `pos`, start of the pulse (in pixel)
# 2: `pulse_size`, number of pixels of the pulse
# 3: `low_size`, number of pixel until next pos - full cycle is 2 + 3
# 4: `nb_pulse`, number of pulses, or `-1` for infinite
#@ solidify:CrenelPositionAnimation,weak
class CrenelPositionAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions with constraints
static var PARAMS = {
"color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000},
"pos": {"default": 0},
"pulse_size": {"min": 0, "default": 1},
"low_size": {"min": 0, "default": 3},
"nb_pulse": {"default": -1}
}
# Render the crenel pattern to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
var pixel_size = frame.width
# Access parameters via virtual members (automatically resolves ValueProviders)
var back_color = self.back_color
var pos = self.pos
var pulse_size = self.pulse_size
var low_size = self.low_size
var nb_pulse = self.nb_pulse
var color = self.color
var period = int(pulse_size + low_size)
# Fill background if not transparent
if back_color != 0xFF000000
frame.fill_pixels(back_color)
end
# Ensure we have a meaningful period
if period <= 0
period = 1
end
# Nothing to paint if nb_pulse is 0
if nb_pulse == 0
return true
end
# For infinite pulses, optimize starting position
if nb_pulse < 0
# Find the position of the first visible falling range (pos + pulse_size - 1)
pos = ((pos + pulse_size - 1) % period) - pulse_size + 1
else
# For finite pulses, skip periods that are completely before the visible area
while (pos < -period) && (nb_pulse != 0)
pos += period
nb_pulse -= 1
end
end
# Render pulses
while (pos < pixel_size) && (nb_pulse != 0)
var i = 0
if pos < 0
i = -pos
end
# Invariant: pos + i >= 0
# Draw the pulse pixels
while (i < pulse_size) && (pos + i < pixel_size)
frame.set_pixel_color(pos + i, color)
i += 1
end
# Move to next pulse position
pos += period
nb_pulse -= 1
end
return true
end
# NO setter/getter methods - use direct assignment instead:
# obj.color = value
# obj.back_color = value
# obj.pos = value
# obj.pulse_size = value
# obj.low_size = value
# obj.nb_pulse = value
# String representation of the animation
def tostring()
var color_str
var raw_color = self.get_param("color")
if animation.is_value_provider(raw_color)
color_str = str(raw_color)
else
color_str = f"0x{self.color :08x}"
end
return f"CrenelPositionAnimation(color={color_str}, pos={self.pos}, pulse_size={self.pulse_size}, low_size={self.low_size}, nb_pulse={self.nb_pulse}, priority={self.priority}, running={self.is_running})"
end
end
return {'crenel_position_animation': CrenelPositionAnimation}

View File

@ -0,0 +1,263 @@
# Fire animation effect for Berry Animation Framework
#
# This animation creates a realistic fire effect with flickering flames.
# The fire uses random intensity variations and warm colors to simulate flames.
#@ solidify:FireAnimation,weak
class FireAnimation : animation.animation
# Non-parameter instance variables only
var heat_map # Array storing heat values for each pixel (0-255)
var current_colors # Array of current colors for each pixel
var last_update # Last update time for flicker timing
var random_seed # Seed for random number generation
# Parameter definitions following parameterized class specification
static var PARAMS = {
"color": {"default": nil},
"intensity": {"min": 0, "max": 255, "default": 180},
"flicker_speed": {"min": 1, "max": 20, "default": 8},
"flicker_amount": {"min": 0, "max": 255, "default": 100},
"cooling_rate": {"min": 0, "max": 255, "default": 55},
"sparking_rate": {"min": 0, "max": 255, "default": 120}
}
# Initialize a new Fire animation
#
# @param engine: AnimationEngine - The animation engine (required)
def init(engine)
# Call parent constructor with engine
super(self).init(engine)
# Initialize non-parameter instance variables only
self.heat_map = []
self.current_colors = []
self.last_update = 0
# Initialize random seed using engine time
self.random_seed = self.engine.time_ms % 65536
end
# Initialize buffers based on current strip length
def _initialize_buffers()
var strip_length = self.engine.get_strip_length()
self.heat_map.resize(strip_length)
self.current_colors.resize(strip_length)
# Initialize all pixels to zero heat
var i = 0
while i < strip_length
self.heat_map[i] = 0
self.current_colors[i] = 0xFF000000 # Black with full alpha
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
# No special handling needed - parameters are accessed via virtual members
# The default fire palette is set up by factory methods when needed
end
# Simple pseudo-random number generator
# Uses a linear congruential generator for consistent results
def _random()
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
return self.random_seed
end
# Get random number in range [0, max)
def _random_range(max)
if max <= 0
return 0
end
return self._random() % max
end
# Update animation state based on current time
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
# Call parent update method first
if !super(self).update(time_ms)
return false
end
# Check if it's time to update the fire simulation
# Update frequency is based on flicker_speed (Hz)
var flicker_speed = self.flicker_speed # Cache parameter value
var update_interval = 1000 / flicker_speed # milliseconds between updates
if time_ms - self.last_update >= update_interval
self.last_update = time_ms
self._update_fire_simulation(time_ms)
end
return true
end
# Update the fire simulation
def _update_fire_simulation(time_ms)
# Cache parameter values for performance
var cooling_rate = self.cooling_rate
var sparking_rate = self.sparking_rate
var intensity = self.intensity
var flicker_amount = self.flicker_amount
var color_param = self.color
var strip_length = self.engine.get_strip_length()
# Ensure buffers are correct size
if size(self.heat_map) != strip_length
self._initialize_buffers()
end
# Step 1: Cool down every pixel a little
var i = 0
while i < strip_length
var cooldown = self._random_range(tasmota.scale_uint(cooling_rate, 0, 255, 0, 10) + 2)
if cooldown >= self.heat_map[i]
self.heat_map[i] = 0
else
self.heat_map[i] -= cooldown
end
i += 1
end
# Step 2: Heat from each pixel drifts 'up' and diffuses a little
# Only do this if we have at least 3 pixels
if strip_length >= 3
var k = strip_length - 1
while k >= 2
var heat_avg = (self.heat_map[k-1] + self.heat_map[k-2] + self.heat_map[k-2]) / 3
self.heat_map[k] = heat_avg
k -= 1
end
end
# Step 3: Randomly ignite new 'sparks' of heat near the bottom
if self._random_range(255) < sparking_rate
var spark_pos = self._random_range(7) # Sparks only in bottom 7 pixels
var spark_heat = self._random_range(95) + 160 # Heat between 160-255
if spark_pos < strip_length
self.heat_map[spark_pos] = spark_heat
end
end
# Step 4: Convert heat to colors
i = 0
while i < strip_length
var heat = self.heat_map[i]
# Apply base intensity scaling
heat = tasmota.scale_uint(heat, 0, 255, 0, intensity)
# Add flicker effect
if flicker_amount > 0
var flicker = self._random_range(flicker_amount)
# Randomly add or subtract flicker
if self._random_range(2) == 0
heat = heat + flicker
else
if heat > flicker
heat = heat - flicker
else
heat = 0
end
end
# Clamp to valid range
if heat > 255
heat = 255
end
end
# Get color from provider based on heat value
var color = 0xFF000000 # Default to black
if heat > 0
# Get the color parameter (may be nil for default)
var resolved_color = color_param
# If color is nil, create default fire palette
if resolved_color == nil
# Create default fire palette on demand
var fire_provider = animation.rich_palette(self.engine)
fire_provider.palette = animation.PALETTE_FIRE
fire_provider.cycle_period = 0 # Use value-based color mapping, not time-based
fire_provider.transition_type = 1 # Use sine transition (smooth)
fire_provider.brightness = 255
fire_provider.set_range(0, 255)
resolved_color = fire_provider
end
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(resolved_color) && resolved_color.get_color_for_value != nil
# Use value-based color mapping for heat
color = resolved_color.get_color_for_value(heat, 0)
else
# Use the resolved color and apply heat as brightness scaling
color = resolved_color
# Apply heat as brightness scaling
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
r = tasmota.scale_uint(heat, 0, 255, 0, r)
g = tasmota.scale_uint(heat, 0, 255, 0, g)
b = tasmota.scale_uint(heat, 0, 255, 0, b)
color = (a << 24) | (r << 16) | (g << 8) | b
end
end
self.current_colors[i] = color
i += 1
end
end
# Render the fire to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var strip_length = self.engine.get_strip_length()
# Render each pixel with its current color
var i = 0
while i < strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# Override start method for timing control
def start(time_ms)
# Call parent start first
super(self).start(time_ms)
# Reset timing and reinitialize buffers
self.last_update = 0
self._initialize_buffers()
# Reset random seed
self.random_seed = self.engine.time_ms % 65536
return self
end
# String representation of the animation
def tostring()
return f"FireAnimation(intensity={self.intensity}, flicker_speed={self.flicker_speed}, priority={self.priority}, running={self.is_running})"
end
end
return {'fire_animation': FireAnimation}

View File

@ -0,0 +1,270 @@
# Gradient animation effect for Berry Animation Framework
#
# This animation creates smooth color gradients that can be linear or radial,
# with optional movement and color transitions over time.
#@ solidify:GradientAnimation,weak
class GradientAnimation : animation.animation
# Non-parameter instance variables only
var current_colors # Array of current colors for each pixel
var phase_offset # Current phase offset for movement
# Parameter definitions following parameterized class specification
static var PARAMS = {
"color": {"default": nil, "nillable": true},
"gradient_type": {"min": 0, "max": 1, "default": 0},
"direction": {"min": 0, "max": 255, "default": 0},
"center_pos": {"min": 0, "max": 255, "default": 128},
"spread": {"min": 1, "max": 255, "default": 255},
"movement_speed": {"min": 0, "max": 255, "default": 0}
}
# Initialize a new Gradient animation
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize non-parameter instance variables only
self.current_colors = []
self.phase_offset = 0
# Initialize with default strip length from engine
var strip_length = self.engine.get_strip_length()
self.current_colors.resize(strip_length)
# Initialize colors to black
var i = 0
while i < strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
# No special handling needed for most parameters
# The virtual parameter system handles storage and validation
# Handle strip length changes from engine
var current_strip_length = self.engine.get_strip_length()
if size(self.current_colors) != current_strip_length
self.current_colors.resize(current_strip_length)
var i = size(self.current_colors)
while i < current_strip_length
if i >= size(self.current_colors) || self.current_colors[i] == nil
if i < size(self.current_colors)
self.current_colors[i] = 0xFF000000
end
end
i += 1
end
end
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Cache parameter values for performance
var movement_speed = self.movement_speed
# Update movement phase if movement is enabled
if movement_speed > 0
var elapsed = time_ms - self.start_time
# Movement speed: 0-255 maps to 0-10 cycles per second
var cycles_per_second = tasmota.scale_uint(movement_speed, 0, 255, 0, 10)
if cycles_per_second > 0
self.phase_offset = (elapsed * cycles_per_second / 1000) % 256
end
end
# Calculate gradient colors
self._calculate_gradient(time_ms)
return true
end
# Calculate gradient colors for all pixels
def _calculate_gradient(time_ms)
# Cache parameter values for performance
var gradient_type = self.gradient_type
var color_param = self.color
var strip_length = self.engine.get_strip_length()
# Ensure current_colors array matches strip length
if size(self.current_colors) != strip_length
self.current_colors.resize(strip_length)
end
var i = 0
while i < strip_length
var gradient_pos = 0
if gradient_type == 0
# Linear gradient
gradient_pos = self._calculate_linear_position(i, strip_length)
else
# Radial gradient
gradient_pos = self._calculate_radial_position(i, strip_length)
end
# Apply movement offset
gradient_pos = (gradient_pos + self.phase_offset) % 256
# Get color from provider
var color = 0xFF000000
# Handle default rainbow gradient if color is nil
if color_param == nil
# Create default rainbow gradient on-the-fly
var hue = tasmota.scale_uint(gradient_pos, 0, 255, 0, 359)
import light_state
var ls = light_state(3) # Create RGB light state
ls.HsToRgb(hue, 255) # Convert HSV to RGB
color = 0xFF000000 | (ls.r << 16) | (ls.g << 8) | ls.b
elif animation.is_color_provider(color_param) && color_param.get_color_for_value != nil
color = color_param.get_color_for_value(gradient_pos, 0)
elif animation.is_value_provider(color_param)
# Use resolve_value with position influence
color = self.resolve_value(color_param, "color", time_ms + gradient_pos * 10)
elif type(color_param) == "int"
# Single color - create gradient from black to color
var intensity = gradient_pos
var r = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 16) & 0xFF)
var g = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 8) & 0xFF)
var b = tasmota.scale_uint(intensity, 0, 255, 0, color_param & 0xFF)
color = 0xFF000000 | (r << 16) | (g << 8) | b
else
color = color_param
end
self.current_colors[i] = color
i += 1
end
end
# Calculate position for linear gradient
def _calculate_linear_position(pixel, strip_length)
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
# Cache parameter values
var direction = self.direction
var spread = self.spread
# Apply direction (0=left-to-right, 128=center-out, 255=right-to-left)
if direction <= 128
# Forward direction with varying start point
var start_offset = tasmota.scale_uint(direction, 0, 128, 0, 128)
strip_pos = (strip_pos + start_offset) % 256
else
# Reverse direction
var reverse_amount = tasmota.scale_uint(direction, 128, 255, 0, 255)
strip_pos = 255 - ((strip_pos + reverse_amount) % 256)
end
# Apply spread (compress or expand the gradient)
strip_pos = tasmota.scale_uint(strip_pos, 0, 255, 0, spread)
return strip_pos
end
# Calculate position for radial gradient
def _calculate_radial_position(pixel, strip_length)
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
# Cache parameter values
var center = self.center_pos
var spread = self.spread
# Calculate distance from center
var distance = 0
if strip_pos >= center
distance = strip_pos - center
else
distance = center - strip_pos
end
# Scale distance by spread
distance = tasmota.scale_uint(distance, 0, 128, 0, spread)
if distance > 255
distance = 255
end
return distance
end
# Render gradient to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var strip_length = self.engine.get_strip_length()
var i = 0
while i < strip_length && i < frame.width
if i < size(self.current_colors)
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var gradient_type = self.gradient_type
var color = self.color
var movement_speed = self.movement_speed
var priority = self.priority
var type_str = gradient_type == 0 ? "linear" : "radial"
var color_str
if animation.is_value_provider(color)
color_str = str(color)
elif color == nil
color_str = "rainbow"
else
color_str = f"0x{color :08x}"
end
return f"GradientAnimation({type_str}, color={color_str}, movement={movement_speed}, priority={priority}, running={self.is_running})"
end
end
# Factory functions following parameterized class specification
# Create a rainbow linear gradient
def gradient_rainbow_linear(engine)
var anim = animation.gradient_animation(engine)
anim.color = nil # Default rainbow
anim.gradient_type = 0 # Linear
anim.direction = 0 # Left-to-right
anim.movement_speed = 50 # Medium movement
return anim
end
# Create a rainbow radial gradient
def gradient_rainbow_radial(engine)
var anim = animation.gradient_animation(engine)
anim.color = nil # Default rainbow
anim.gradient_type = 1 # Radial
anim.center_pos = 128 # Center
anim.movement_speed = 30 # Slow movement
return anim
end
# Create a two-color linear gradient
def gradient_two_color_linear(engine)
var anim = animation.gradient_animation(engine)
anim.color = 0xFFFF0000 # Default red gradient
anim.gradient_type = 0 # Linear
anim.direction = 0 # Left-to-right
anim.movement_speed = 0 # Static
return anim
end
return {'gradient_animation': GradientAnimation, 'gradient_rainbow_linear': gradient_rainbow_linear, 'gradient_rainbow_radial': gradient_rainbow_radial, 'gradient_two_color_linear': gradient_two_color_linear}

View File

@ -0,0 +1,302 @@
# Jitter animation effect for Berry Animation Framework
#
# This animation adds random jitter/shake effects to patterns with configurable
# intensity, frequency, and jitter types (position, color, brightness).
#@ solidify:JitterAnimation,weak
class JitterAnimation : animation.animation
# Non-parameter instance variables only
var random_seed # Seed for random number generation
var last_jitter_time # Last time jitter was updated
var jitter_offsets # Array of current jitter offsets per pixel
var source_frame # Frame buffer for source animation
var current_colors # Array of current colors for each pixel
# Parameter definitions
static var PARAMS = {
"source_animation": {"type": "instance", "default": nil},
"jitter_intensity": {"min": 0, "max": 255, "default": 100},
"jitter_frequency": {"min": 0, "max": 255, "default": 60},
"jitter_type": {"min": 0, "max": 3, "default": 0},
"position_range": {"min": 0, "max": 255, "default": 50},
"color_range": {"min": 0, "max": 255, "default": 30},
"brightness_range": {"min": 0, "max": 255, "default": 40}
}
# Initialize a new Jitter animation
def init(engine)
# Call parent constructor with engine
super(self).init(engine)
# Initialize random seed using engine time
self.random_seed = self.engine.time_ms % 65536
# Initialize state
self.last_jitter_time = 0
# Initialize buffers
self._initialize_buffers()
end
# Initialize buffers based on current strip length
def _initialize_buffers()
var current_strip_length = self.engine.get_strip_length()
self.jitter_offsets = []
self.jitter_offsets.resize(current_strip_length)
self.source_frame = animation.frame_buffer(current_strip_length)
self.current_colors = []
self.current_colors.resize(current_strip_length)
# Initialize arrays
var i = 0
while i < current_strip_length
self.jitter_offsets[i] = 0
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Override start method for lifecycle control
def start(time_ms)
# Call parent start first (handles ValueProvider propagation)
super(self).start(time_ms)
# Reset jitter timing
self.last_jitter_time = time_ms != nil ? time_ms : self.engine.time_ms
# Reinitialize buffers in case strip length changed
self._initialize_buffers()
return self
end
# Simple pseudo-random number generator
def _random()
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
return self.random_seed
end
# Get random number in range [-max_range, max_range]
def _random_range(max_range)
if max_range <= 0
return 0
end
var val = self._random() % (max_range * 2 + 1)
return val - max_range
end
# Update animation state
def update(time_ms)
# Cache parameter values for performance
var jitter_frequency = self.jitter_frequency
var source_animation = self.source_animation
# Update jitter at specified frequency
if jitter_frequency > 0
# Frequency: 0-255 maps to 0-30 Hz
var hz = tasmota.scale_uint(jitter_frequency, 0, 255, 0, 30)
var interval = hz > 0 ? 1000 / hz : 1000
if time_ms - self.last_jitter_time >= interval
self.last_jitter_time = time_ms
self._update_jitter()
end
end
# Update source animation if it exists
if source_animation != nil
source_animation.update(time_ms)
end
# Calculate jittered colors
self._calculate_jitter()
return true
end
# Update jitter offsets
def _update_jitter()
var current_strip_length = self.engine.get_strip_length()
var jitter_intensity = self.jitter_intensity
var max_offset = tasmota.scale_uint(jitter_intensity, 0, 255, 0, 10)
var i = 0
while i < current_strip_length
# Generate new random offset based on intensity
self.jitter_offsets[i] = self._random_range(max_offset)
i += 1
end
end
# Calculate jittered colors for all pixels
def _calculate_jitter()
var current_strip_length = self.engine.get_strip_length()
var source_animation = self.source_animation
var jitter_type = self.jitter_type
var position_range = self.position_range
# Clear source frame
self.source_frame.clear()
# Render source animation to frame
if source_animation != nil
source_animation.render(self.source_frame, 0)
end
# Apply jitter transformation
var i = 0
while i < current_strip_length
var base_color = 0xFF000000
if jitter_type == 0 || jitter_type == 3
# Position jitter
var jitter_pixels = tasmota.scale_uint(self.jitter_offsets[i], -10, 10, -position_range / 10, position_range / 10)
var source_pos = i + jitter_pixels
# Clamp to strip bounds
if source_pos >= 0 && source_pos < current_strip_length
base_color = self.source_frame.get_pixel_color(source_pos)
else
base_color = 0xFF000000
end
else
# No position jitter, use original position
base_color = self.source_frame.get_pixel_color(i)
end
# Apply color and brightness jitter
if (jitter_type == 1 || jitter_type == 2 || jitter_type == 3) && base_color != 0xFF000000
base_color = self._apply_color_jitter(base_color, i)
end
self.current_colors[i] = base_color
i += 1
end
end
# Apply color/brightness jitter to a color
def _apply_color_jitter(color, pixel_index)
# Cache parameter values for performance
var jitter_type = self.jitter_type
var color_range = self.color_range
var brightness_range = self.brightness_range
# Extract ARGB components
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
if jitter_type == 1 || jitter_type == 3
# Color jitter - add random values to RGB
var color_jitter = tasmota.scale_uint(color_range, 0, 255, 0, 30)
r += self._random_range(color_jitter)
g += self._random_range(color_jitter)
b += self._random_range(color_jitter)
end
if jitter_type == 2 || jitter_type == 3
# Brightness jitter - scale all RGB components
var brightness_jitter = tasmota.scale_uint(brightness_range, 0, 255, 0, 50)
var brightness_factor = 128 + self._random_range(brightness_jitter)
if brightness_factor < 0
brightness_factor = 0
elif brightness_factor > 255
brightness_factor = 255
end
r = tasmota.scale_uint(r, 0, 255, 0, brightness_factor)
g = tasmota.scale_uint(g, 0, 255, 0, brightness_factor)
b = tasmota.scale_uint(b, 0, 255, 0, brightness_factor)
end
# Clamp components to valid range
if r > 255
r = 255
elif r < 0
r = 0
end
if g > 255
g = 255
elif g < 0
g = 0
end
if b > 255
b = 255
elif b < 0
b = 0
end
return (a << 24) | (r << 16) | (g << 8) | b
end
# Render jitter to frame buffer
def render(frame, time_ms)
if frame == nil
return false
end
var current_strip_length = self.engine.get_strip_length()
var i = 0
while i < current_strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var type_names = ["position", "color", "brightness", "all"]
var jitter_type = self.jitter_type
var type_name = type_names[jitter_type] != nil ? type_names[jitter_type] : "unknown"
return f"JitterAnimation({type_name}, intensity={self.jitter_intensity}, frequency={self.jitter_frequency})"
end
end
# Factory functions for common jitter presets
# Create a position jitter animation
def jitter_position(engine)
var anim = animation.jitter_animation(engine)
anim.jitter_type = 0
anim.position_range = 50
return anim
end
# Create a color jitter animation
def jitter_color(engine)
var anim = animation.jitter_animation(engine)
anim.jitter_type = 1
anim.color_range = 30
return anim
end
# Create a brightness jitter animation
def jitter_brightness(engine)
var anim = animation.jitter_animation(engine)
anim.jitter_type = 2
anim.brightness_range = 40
return anim
end
# Create a full jitter animation (all types)
def jitter_all(engine)
var anim = animation.jitter_animation(engine)
anim.jitter_type = 3
anim.position_range = 50
anim.color_range = 30
anim.brightness_range = 40
return anim
end
return {
'jitter_animation': JitterAnimation,
'jitter_position': jitter_position,
'jitter_color': jitter_color,
'jitter_brightness': jitter_brightness,
'jitter_all': jitter_all
}

View File

@ -0,0 +1,316 @@
# Noise animation effect for Berry Animation Framework
#
# This animation creates pseudo-random noise patterns with configurable
# scale, speed, and color mapping through palettes or single colors.
#@ solidify:NoiseAnimation,weak
class NoiseAnimation : animation.animation
# Non-parameter instance variables only
var current_colors # Array of current colors for each pixel
var time_offset # Current time offset for animation
var noise_table # Pre-computed noise values for performance
# Parameter definitions following new specification
static var PARAMS = {
"color": {"default": nil},
"scale": {"min": 1, "max": 255, "default": 50},
"speed": {"min": 0, "max": 255, "default": 30},
"octaves": {"min": 1, "max": 4, "default": 1},
"persistence": {"min": 0, "max": 255, "default": 128},
"seed": {"min": 0, "max": 65535, "default": 12345}
}
# Initialize a new Noise animation
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize non-parameter instance variables only
var strip_length = self.engine.get_strip_length()
self.current_colors = []
self.current_colors.resize(strip_length)
self.time_offset = 0
# Initialize colors to black
var i = 0
while i < strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
# Initialize noise table - will be done in start method
self.noise_table = []
# Set default color if not set
if self.color == nil
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1
rainbow_provider.brightness = 255
rainbow_provider.range_min = 0
rainbow_provider.range_max = 255
self.color = rainbow_provider
end
end
# Override start method for initialization
def start(time_ms)
# Call parent start first
super(self).start(time_ms)
# Initialize noise table with current seed
self._init_noise_table()
# Reset time offset
self.time_offset = 0
return self
end
# Initialize noise lookup table for performance
def _init_noise_table()
self.noise_table = []
self.noise_table.resize(256)
# Generate pseudo-random values using seed
var current_seed = self.seed
var rng_state = current_seed
var i = 0
while i < 256
rng_state = (rng_state * 1103515245 + 12345) & 0x7FFFFFFF
self.noise_table[i] = rng_state % 256
i += 1
end
end
# Override setmember to handle color conversion
def setmember(name, value)
if name == "color" && type(value) == "int"
# Convert integer color to gradient palette from black to color
var palette = bytes()
palette.add(0x00, 1) # Position 0: black
palette.add(0x00, 1) # R
palette.add(0x00, 1) # G
palette.add(0x00, 1) # B
palette.add(0xFF, 1) # Position 255: full color
palette.add((value >> 16) & 0xFF, 1) # R
palette.add((value >> 8) & 0xFF, 1) # G
palette.add(value & 0xFF, 1) # B
var gradient_provider = animation.rich_palette(self.engine)
gradient_provider.palette = palette
gradient_provider.cycle_period = 5000
gradient_provider.transition_type = 1
gradient_provider.brightness = 255
gradient_provider.range_min = 0
gradient_provider.range_max = 255
# Set the gradient provider instead of the integer
super(self).setmember(name, gradient_provider)
else
# Use parent implementation for other parameters
super(self).setmember(name, value)
end
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "seed"
self._init_noise_table()
end
# Update current_colors array size when strip length changes via engine
var new_strip_length = self.engine.get_strip_length()
if size(self.current_colors) != new_strip_length
self.current_colors.resize(new_strip_length)
var i = size(self.current_colors)
while i < new_strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
end
# Simple noise function using lookup table
def _noise_1d(x)
var ix = int(x) & 255
var fx = x - int(x)
# Get noise values at integer positions
var a = self.noise_table[ix]
var b = self.noise_table[(ix + 1) & 255]
# Linear interpolation using integer math
var lerp_amount = tasmota.scale_uint(int(fx * 256), 0, 256, 0, 255)
return tasmota.scale_uint(lerp_amount, 0, 255, a, b)
end
# Fractal noise with multiple octaves
def _fractal_noise(x, time_offset)
var value = 0
var amplitude = 255
var current_scale = self.scale
var current_octaves = self.octaves
var current_persistence = self.persistence
var frequency = current_scale
var max_value = 0
var octave = 0
while octave < current_octaves
var sample_x = tasmota.scale_uint(x * frequency, 0, 255 * 255, 0, 255) + time_offset
var noise_val = self._noise_1d(sample_x)
value += tasmota.scale_uint(noise_val, 0, 255, 0, amplitude)
max_value += amplitude
amplitude = tasmota.scale_uint(amplitude, 0, 255, 0, current_persistence)
frequency = frequency * 2
if frequency > 255
frequency = 255
end
octave += 1
end
# Normalize to 0-255 range
if max_value > 0
value = tasmota.scale_uint(value, 0, max_value, 0, 255)
end
return value
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Update time offset based on speed
var current_speed = self.speed
if current_speed > 0
var elapsed = time_ms - self.start_time
# Speed: 0-255 maps to 0-5 units per second
var units_per_second = tasmota.scale_uint(current_speed, 0, 255, 0, 5)
if units_per_second > 0
self.time_offset = (elapsed * units_per_second / 1000) % 256
end
end
# Calculate noise colors
self._calculate_noise(time_ms)
return true
end
# Calculate noise colors for all pixels
def _calculate_noise(time_ms)
var strip_length = self.engine.get_strip_length()
var current_color = self.color
var i = 0
while i < strip_length
# Calculate noise value for this pixel
var noise_value = self._fractal_noise(i, self.time_offset)
# Get color from provider
var color = 0xFF000000
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
color = current_color.get_color_for_value(noise_value, 0)
else
# Use resolve_value with noise influence
color = self.resolve_value(current_color, "color", time_ms + noise_value * 10)
end
self.current_colors[i] = color
i += 1
end
end
# Render noise to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var strip_length = self.engine.get_strip_length()
var i = 0
while i < strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var current_color = self.color
var color_str
if animation.is_value_provider(current_color)
color_str = str(current_color)
else
color_str = f"0x{current_color :08x}"
end
return f"NoiseAnimation(color={color_str}, scale={self.scale}, speed={self.speed}, octaves={self.octaves}, priority={self.priority}, running={self.is_running})"
end
end
# Factory functions following new specification
# Create a rainbow noise animation preset
def noise_rainbow(engine)
var anim = animation.noise_animation(engine)
# Set up rainbow color provider
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1
rainbow_provider.brightness = 255
rainbow_provider.range_min = 0
rainbow_provider.range_max = 255
anim.color = rainbow_provider
anim.scale = 50
anim.speed = 30
anim.octaves = 1
return anim
end
# Create a single color noise animation preset
def noise_single_color(engine)
var anim = animation.noise_animation(engine)
# Set up a simple white color - user can change it after creation
anim.color = 0xFFFFFFFF
anim.scale = 50
anim.speed = 30
anim.octaves = 1
return anim
end
# Create a fractal noise animation preset
def noise_fractal(engine)
var anim = animation.noise_animation(engine)
# Set up rainbow color provider
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1
rainbow_provider.brightness = 255
rainbow_provider.range_min = 0
rainbow_provider.range_max = 255
anim.color = rainbow_provider
anim.scale = 30
anim.speed = 20
anim.octaves = 3
anim.persistence = 128
return anim
end
return {'noise_animation': NoiseAnimation, 'noise_rainbow': noise_rainbow, 'noise_single_color': noise_single_color, 'noise_fractal': noise_fractal}

View File

@ -0,0 +1,308 @@
# PalettePattern animation effect for Berry Animation Framework
#
# This animation applies colors from a color provider to specific patterns or regions.
# It allows for more complex visual effects by combining palette colors with patterns.
#
# This version supports both RichPaletteAnimation and ColorProvider instances as color sources,
# allowing for more flexible usage of color providers.
#@ solidify:PalettePatternAnimation,weak
class PalettePatternAnimation : animation.animation
var value_buffer # Buffer to store values for each pixel
# Static definitions of parameters with constraints
static var PARAMS = {
# Palette pattern-specific parameters
"color_source": {"default": nil, "type": "instance"},
"pattern_func": {"default": nil, "type": "function"}
}
# Initialize a new PalettePattern animation
#
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor with engine
super(self).init(engine)
# Initialize non-parameter instance variables only
self.value_buffer = []
# Initialize value buffer with default frame width
self._initialize_value_buffer()
end
# Initialize the value buffer based on current strip length
def _initialize_value_buffer()
var strip_length = self.engine.get_strip_length()
self.value_buffer.resize(strip_length)
# Initialize with zeros
var i = 0
while i < strip_length
self.value_buffer[i] = 0
i += 1
end
end
# Update the value buffer based on the current time
#
# @param time_ms: int - Current time in milliseconds
def _update_value_buffer(time_ms)
var pattern_func = self.pattern_func
if pattern_func == nil
return
end
var strip_length = self.engine.get_strip_length()
# Resize buffer if strip length changed
if size(self.value_buffer) != strip_length
self.value_buffer.resize(strip_length)
end
# Calculate values for each pixel
var i = 0
while i < strip_length
self.value_buffer[i] = pattern_func(i, time_ms, self)
i += 1
end
end
# Update animation state based on current time
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
# Call parent update method first
if !super(self).update(time_ms)
return false
end
# Calculate elapsed time since animation started
var elapsed = time_ms - self.start_time
# Update the value buffer
self._update_value_buffer(elapsed)
return true
end
# Render the pattern to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Use provided time or default to engine time
if time_ms == nil
time_ms = self.engine.time_ms
end
# Get current parameter values (cached for performance)
var color_source = self.color_source
if color_source == nil
return false
end
# Calculate elapsed time since animation started
var elapsed = time_ms - self.start_time
# Apply colors from the color source to each pixel based on its value
var strip_length = self.engine.get_strip_length()
var i = 0
while i < strip_length && i < frame.width
var value = self.value_buffer[i]
var color
# Check if color_source is a ColorProvider or an animation with get_color_for_value method
if color_source.get_color_for_value != nil
# It's a ColorProvider or compatible object
color = color_source.get_color_for_value(value, elapsed)
else
# Fallback to direct color access (for backward compatibility)
color = color_source.current_color
end
frame.set_pixel_color(i, color)
i += 1
end
return true
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "pattern_func" || name == "color_source"
# Reinitialize value buffer when pattern or color source changes
self._initialize_value_buffer()
end
end
# String representation of the animation
def tostring()
var strip_length = self.engine.get_strip_length()
return f"PalettePatternAnimation(strip_length={strip_length}, priority={self.priority}, running={self.is_running})"
end
end
# Wave pattern animation - creates sine wave patterns
#@ solidify:PaletteWaveAnimation,weak
class PaletteWaveAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints
static var PARAMS = {
# Wave-specific parameters only
"wave_period": {"min": 1, "default": 5000},
"wave_length": {"min": 1, "default": 10}
}
# Initialize a new wave pattern animation
#
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor
super(self).init(engine)
# Set default name
self.name = "palette_wave"
end
# Override _update_value_buffer to generate wave pattern directly
def _update_value_buffer(time_ms)
# Cache parameter values for performance
var wave_period = self.wave_period
var wave_length = self.wave_length
var strip_length = self.engine.get_strip_length()
# Resize buffer if strip length changed
if size(self.value_buffer) != strip_length
self.value_buffer.resize(strip_length)
end
# Calculate the wave position using scale_uint for better precision
var position = tasmota.scale_uint(time_ms % wave_period, 0, wave_period, 0, 1000) / 1000.0
var offset = int(position * wave_length)
# Calculate values for each pixel
var i = 0
while i < strip_length
# Calculate the wave value (0-100) using scale_uint
var pos_in_wave = (i + offset) % wave_length
var angle = tasmota.scale_uint(pos_in_wave, 0, wave_length, 0, 32767) # 0 to 2π in fixed-point
var sine_value = tasmota.sine_int(angle) # -4096 to 4096
# Map sine value from -4096..4096 to 0..100
self.value_buffer[i] = tasmota.scale_int(sine_value, -4096, 4096, 0, 100)
i += 1
end
end
end
# Gradient pattern animation - creates shifting gradient patterns
#@ solidify:PaletteGradientAnimation,weak
class PaletteGradientAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints
static var PARAMS = {
# Gradient-specific parameters only
"shift_period": {"min": 1, "default": 10000}
}
# Initialize a new gradient pattern animation
#
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor
super(self).init(engine)
# Set default name
self.name = "palette_gradient"
end
# Override _update_value_buffer to generate gradient pattern directly
def _update_value_buffer(time_ms)
# Cache parameter values for performance
var shift_period = self.shift_period
var strip_length = self.engine.get_strip_length()
# Resize buffer if strip length changed
if size(self.value_buffer) != strip_length
self.value_buffer.resize(strip_length)
end
# Calculate the shift position using scale_uint for better precision
var position = tasmota.scale_uint(time_ms % shift_period, 0, shift_period, 0, 1000) / 1000.0
var offset = int(position * strip_length)
# Calculate values for each pixel
var i = 0
while i < strip_length
# Calculate the gradient value (0-100) using scale_uint
var pos_in_frame = (i + offset) % strip_length
self.value_buffer[i] = tasmota.scale_uint(pos_in_frame, 0, strip_length - 1, 0, 100)
i += 1
end
end
end
# Value meter pattern animation - creates meter/bar patterns based on a value function
#@ solidify:PaletteMeterAnimation,weak
class PaletteMeterAnimation : PalettePatternAnimation
# Static definitions of parameters with constraints
static var PARAMS = {
# Meter-specific parameters only
"value_func": {"default": nil, "type": "function"}
}
# Initialize a new meter pattern animation
#
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor
super(self).init(engine)
# Set default name
self.name = "palette_meter"
end
# Override _update_value_buffer to generate meter pattern directly
def _update_value_buffer(time_ms)
# Cache parameter values for performance
var value_func = self.value_func
if value_func == nil
return
end
var strip_length = self.engine.get_strip_length()
# Resize buffer if strip length changed
if size(self.value_buffer) != strip_length
self.value_buffer.resize(strip_length)
end
# Get the current value
var current_value = value_func(time_ms, self)
# Calculate the meter position using scale_uint for better precision
var meter_position = tasmota.scale_uint(current_value, 0, 100, 0, strip_length)
# Calculate values for each pixel
var i = 0
while i < strip_length
# Return 100 if pixel is within the meter, 0 otherwise
self.value_buffer[i] = i < meter_position ? 100 : 0
i += 1
end
end
end
return {
'palette_pattern_animation': PalettePatternAnimation,
'palette_wave_animation': PaletteWaveAnimation,
'palette_gradient_animation': PaletteGradientAnimation,
'palette_meter_animation': PaletteMeterAnimation
}

View File

@ -0,0 +1,249 @@
# Plasma animation effect for Berry Animation Framework
#
# This animation creates classic plasma effects using sine wave interference
# patterns with configurable frequencies, phases, and time-based animation.
#@ solidify:PlasmaAnimation,weak
class PlasmaAnimation : animation.animation
# Non-parameter instance variables only
var current_colors # Array of current colors for each pixel
var time_phase # Current time-based phase
# Parameter definitions following parameterized class specification
static var PARAMS = {
"color": {"default": nil},
"freq_x": {"min": 1, "max": 255, "default": 32},
"freq_y": {"min": 1, "max": 255, "default": 23},
"phase_x": {"min": 0, "max": 255, "default": 0},
"phase_y": {"min": 0, "max": 255, "default": 64},
"time_speed": {"min": 0, "max": 255, "default": 50},
"blend_mode": {"min": 0, "max": 2, "default": 0}
}
# Initialize a new Plasma animation
#
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor with engine
super(self).init(engine)
# Initialize non-parameter instance variables only
self.time_phase = 0
# Initialize current_colors array - will be resized when strip length is known
self.current_colors = []
self._initialize_colors()
end
# Fast sine calculation using Tasmota's optimized sine function
# Input: angle in 0-255 range (mapped to 0-2π)
# Output: sine value in 0-255 range (mapped from -1 to 1)
def _sine(angle)
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
var tasmota_angle = tasmota.scale_uint(angle, 0, 255, 0, 32767)
# Get sine value from -4096 to 4096 (representing -1.0 to 1.0)
var sine_val = tasmota.sine_int(tasmota_angle)
# Map from -4096..4096 to 0..255 for plasma calculations
return tasmota.scale_uint(sine_val, -4096, 4096, 0, 255)
end
# Initialize colors array based on current strip length
def _initialize_colors()
var strip_length = self.engine.get_strip_length()
self.current_colors.resize(strip_length)
var i = 0
while i < strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Start/restart the animation
def start(time_ms)
# Call parent start first
super(self).start(time_ms)
# Initialize default color if not set
if self.color == nil
var rainbow_provider = animation.rich_palette(self.engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1
rainbow_provider.brightness = 255
rainbow_provider.range_min = 0
rainbow_provider.range_max = 255
self.color = rainbow_provider
end
# Reset time phase
self.time_phase = 0
return self
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color" && value == nil
# Reset to default rainbow palette when color is set to nil
var rainbow_provider = animation.rich_palette(self.engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1
rainbow_provider.brightness = 255
rainbow_provider.range_min = 0
rainbow_provider.range_max = 255
# Set the parameter directly to avoid recursion
self.set_param("color", rainbow_provider)
end
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Update time phase based on speed
var current_time_speed = self.time_speed
if current_time_speed > 0
var elapsed = time_ms - self.start_time
# Speed: 0-255 maps to 0-8 cycles per second
var cycles_per_second = tasmota.scale_uint(current_time_speed, 0, 255, 0, 8)
if cycles_per_second > 0
self.time_phase = (elapsed * cycles_per_second / 1000) % 256
end
end
# Calculate plasma colors
self._calculate_plasma(time_ms)
return true
end
# Calculate plasma colors for all pixels
def _calculate_plasma(time_ms)
var strip_length = self.engine.get_strip_length()
# Ensure colors array is properly sized
if size(self.current_colors) != strip_length
self._initialize_colors()
end
# Cache parameter values for performance
var current_freq_x = self.freq_x
var current_freq_y = self.freq_y
var current_phase_x = self.phase_x
var current_phase_y = self.phase_y
var current_blend_mode = self.blend_mode
var current_color = self.color
var i = 0
while i < strip_length
# Map pixel position to 0-255 range
var x = tasmota.scale_uint(i, 0, strip_length - 1, 0, 255)
# Calculate plasma components
var comp1 = self._sine((x * current_freq_x / 32) + current_phase_x + self.time_phase)
var comp2 = self._sine((x * current_freq_y / 32) + current_phase_y + (self.time_phase * 2))
# Blend components based on blend mode
var plasma_value = 0
if current_blend_mode == 0
# Add mode
plasma_value = (comp1 + comp2) / 2
elif current_blend_mode == 1
# Multiply mode
plasma_value = tasmota.scale_uint(comp1, 0, 255, 0, comp2)
else
# Average mode (default)
plasma_value = (comp1 + comp2) / 2
end
# Ensure value is in valid range
if plasma_value > 255
plasma_value = 255
elif plasma_value < 0
plasma_value = 0
end
# Get color from provider
var color = 0xFF000000
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
color = current_color.get_color_for_value(plasma_value, 0)
else
# Use resolve_value with plasma influence
color = self.resolve_value(current_color, "color", time_ms + plasma_value * 10)
end
self.current_colors[i] = color
i += 1
end
end
# Render plasma to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var strip_length = self.engine.get_strip_length()
var i = 0
while i < strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var color_str
var current_color = self.color
if animation.is_value_provider(current_color)
color_str = str(current_color)
else
color_str = f"0x{current_color :08x}"
end
return f"PlasmaAnimation(color={color_str}, freq_x={self.freq_x}, freq_y={self.freq_y}, time_speed={self.time_speed}, priority={self.priority}, running={self.is_running})"
end
end
# Factory functions
# Create a classic rainbow plasma animation
#
# @param engine: AnimationEngine - Required animation engine reference
# @return PlasmaAnimation - A new plasma animation instance with rainbow colors
def plasma_rainbow(engine)
var anim = animation.plasma_animation(engine)
# Use default rainbow color (nil triggers rainbow in on_param_changed)
anim.color = nil
anim.time_speed = 50
anim.name = "plasma_rainbow"
return anim
end
# Create a fast plasma animation
#
# @param engine: AnimationEngine - Required animation engine reference
# @return PlasmaAnimation - A new fast plasma animation instance
def plasma_fast(engine)
var anim = animation.plasma_animation(engine)
anim.color = nil # Default rainbow
anim.time_speed = 150
anim.freq_x = 48
anim.freq_y = 35
anim.name = "plasma_fast"
return anim
end
return {'plasma_animation': PlasmaAnimation, 'plasma_rainbow': plasma_rainbow, 'plasma_fast': plasma_fast}

View File

@ -1,208 +0,0 @@
# Pulse Animation for Berry Animation Framework
#
# A pulse animation takes any pattern and makes it pulse between min and max opacity.
# This demonstrates how animations can be composed with patterns.
#@ solidify:PulseAnimation,weak
class PulseAnimation : animation.animation
var base_pattern # The pattern to pulse (can be any Pattern)
var min_opacity # Minimum opacity level (0-255)
var max_opacity # Maximum opacity level (0-255)
var pulse_period # Time for one complete pulse cycle in milliseconds
var current_pulse_opacity # Current pulse opacity (calculated during update)
# Initialize a new Pulse animation
#
# @param base_pattern: Pattern - The pattern to pulse
# @param min_opacity: int - Minimum opacity level (0-255)
# @param max_opacity: int - Maximum opacity level (0-255)
# @param pulse_period: int - Time for one complete pulse cycle in milliseconds
# @param priority: int - Rendering priority (higher = on top)
# @param duration: int - Duration in milliseconds, 0 for infinite
# @param loop: bool - Whether animation should loop when duration is reached
# @param opacity: int - Base animation opacity (0-255), defaults to 255
# @param name: string - Optional name for the animation
def init(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
# Call parent Animation constructor
super(self).init(priority, duration, loop, opacity, name != nil ? name : "pulse")
# Set pulse-specific properties with defaults
self.base_pattern = base_pattern
self.min_opacity = min_opacity != nil ? min_opacity : 0
self.max_opacity = max_opacity != nil ? max_opacity : 255
self.pulse_period = pulse_period != nil ? pulse_period : 1000
self.current_pulse_opacity = self.max_opacity
# Register parameters with validation
self.register_param("base_pattern", {"default": nil})
self.register_param("min_opacity", {"min": 0, "max": 255, "default": 0})
self.register_param("max_opacity", {"min": 0, "max": 255, "default": 255})
self.register_param("pulse_period", {"min": 100, "default": 1000})
# Set initial parameter values
self.set_param("base_pattern", self.base_pattern)
self.set_param("min_opacity", self.min_opacity)
self.set_param("max_opacity", self.max_opacity)
self.set_param("pulse_period", self.pulse_period)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "base_pattern"
self.base_pattern = value
elif name == "min_opacity"
self.min_opacity = value
elif name == "max_opacity"
self.max_opacity = value
elif name == "pulse_period"
self.pulse_period = value
end
end
# Update animation state based on current time
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
# Call parent update method first
if !super(self).update(time_ms)
return false
end
# Update the base pattern if it has an update method
if self.base_pattern != nil && self.base_pattern.update != nil
self.base_pattern.update(time_ms)
end
# Calculate elapsed time since animation started
var elapsed = time_ms - self.start_time
# Calculate position in the pulse cycle (0 to 32767, representing 0 to 2π)
var cycle_position = tasmota.scale_uint(elapsed % self.pulse_period, 0, self.pulse_period, 0, 32767)
# Use fixed-point sine to create smooth pulsing effect
# tasmota.sine_int returns values from -4096 to 4096 (representing -1.0 to 1.0)
# Convert to 0 to 1.0 range by adding 4096 and dividing by 8192
var pulse_factor = tasmota.sine_int(cycle_position) + 4096 # range is 0..8192
# Calculate current pulse opacity based on min/max and pulse factor
self.current_pulse_opacity = tasmota.scale_uint(pulse_factor, 0, 8192, self.min_opacity, self.max_opacity)
return true
end
# Get a color for a specific pixel position and time
# This delegates to the base pattern but applies pulse opacity
#
# @param pixel: int - Pixel index (0-based)
# @param time_ms: int - Current time in milliseconds
# @return int - Color in ARGB format (0xAARRGGBB)
def get_color_at(pixel, time_ms)
if self.base_pattern == nil
return 0x00000000 # Transparent if no base pattern
end
# Get color from base pattern
var base_color = self.base_pattern.get_color_at(pixel, time_ms)
# Apply pulse opacity to the base color
var base_alpha = (base_color >> 24) & 0xFF
var pulsed_alpha = tasmota.scale_uint(self.current_pulse_opacity, 0, 255, 0, base_alpha)
# Resolve and combine with base animation opacity
var current_opacity = self.resolve_value(self.opacity, "opacity", time_ms)
var final_alpha = tasmota.scale_uint(current_opacity, 0, 255, 0, pulsed_alpha)
# Return color with modified alpha
return (base_color & 0x00FFFFFF) | (final_alpha << 24)
end
# Render the pulsing pattern to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Current time in milliseconds
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil || self.base_pattern == nil
return false
end
# Update animation state
self.update(time_ms)
# Let the base pattern render first
var modified = self.base_pattern.render(frame, time_ms)
# Apply pulse opacity to the entire frame
if modified && self.current_pulse_opacity < 255
frame.apply_brightness(self.current_pulse_opacity)
end
# Resolve and apply base animation opacity if not full
var current_opacity = self.resolve_value(self.opacity, "opacity", time_ms)
if modified && current_opacity < 255
frame.apply_brightness(current_opacity)
end
return modified
end
# Set the base pattern
#
# @param pattern: Pattern - The pattern to pulse
# @return self for method chaining
def set_base_pattern(pattern)
self.set_param("base_pattern", pattern)
return self
end
# Set the minimum opacity
#
# @param opacity: int - Minimum opacity level (0-255)
# @return self for method chaining
def set_min_opacity(opacity)
self.set_param("min_opacity", opacity)
return self
end
# Set the maximum opacity
#
# @param opacity: int - Maximum opacity level (0-255)
# @return self for method chaining
def set_max_opacity(opacity)
self.set_param("max_opacity", opacity)
return self
end
# Set the pulse period
#
# @param period: int - Time for one complete pulse cycle in milliseconds
# @return self for method chaining
def set_pulse_period(period)
self.set_param("pulse_period", period)
return self
end
# String representation of the animation
def tostring()
return f"PulseAnimation(base_pattern={self.base_pattern}, min_opacity={self.min_opacity}, max_opacity={self.max_opacity}, pulse_period={self.pulse_period}, priority={self.priority}, running={self.is_running})"
end
end
# Factory function to create a pulse animation
#
# @param base_pattern: Pattern - The pattern to pulse
# @param min_opacity: int - Minimum opacity level (0-255), defaults to 0
# @param max_opacity: int - Maximum opacity level (0-255), defaults to 255
# @param pulse_period: int - Time for one complete pulse cycle in milliseconds, defaults to 1000
# @param priority: int - Rendering priority (higher = on top), defaults to 0
# @param duration: int - Duration in milliseconds, 0 for infinite, defaults to 0
# @param loop: bool - Whether animation should loop when duration is reached, defaults to true
# @param opacity: int - Base animation opacity (0-255), defaults to 255
# @param name: string - Optional name for the animation
# @return PulseAnimation - A new pulse animation instance
def pulse(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
return PulseAnimation(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
end
return {'pulse_animation': PulseAnimation, 'pulse': pulse}

View File

@ -0,0 +1,80 @@
# RichPaletteAnimation - Animation with integrated rich palette color provider
#
# This animation class provides direct access to rich palette parameters,
# forwarding them to an internal RichPaletteColorProvider instance.
# This creates a cleaner API where users can set palette parameters directly
# on the animation instead of accessing nested color provider properties.
#
# Follows the parameterized class specification with parameter forwarding pattern.
#@ solidify:RichPaletteAnimation,weak
class RichPaletteAnimation : animation.animation
# Non-parameter instance variables only
var color_provider # Internal RichPaletteColorProvider instance
# Parameter definitions - only RichPaletteColorProvider parameters (Animation params inherited)
static var PARAMS = {
# RichPaletteColorProvider parameters (forwarded to internal provider)
"palette": {"type": "instance", "default": nil},
"cycle_period": {"min": 0, "default": 5000},
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
"brightness": {"min": 0, "max": 255, "default": 255},
"range_min": {"default": 0},
"range_max": {"default": 100}
}
# Initialize a new RichPaletteAnimation
#
# @param engine: AnimationEngine - Reference to the animation engine (required)
def init(engine)
super(self).init(engine) # Initialize Animation base class
# Set default name (override inherited default)
self.name = "rich_palette"
# Create internal RichPaletteColorProvider instance
self.color_provider = animation.rich_palette(engine)
# Set the color parameter to our internal provider
# Use direct values assignment to avoid triggering on_param_changed
self.values["color"] = self.color_provider
end
# Handle parameter changes - forward rich palette parameters to internal provider
#
# @param name: string - Name of the parameter that changed
# @param value: any - New value of the parameter
def on_param_changed(name, value)
# Forward rich palette parameters to internal color provider
if name == "palette" || name == "cycle_period" || name == "transition_type" ||
name == "brightness" || name == "range_min" || name == "range_max"
# Set parameter on internal color provider
self.color_provider.set_param(name, value)
else
# Let parent handle animation-specific parameters
super(self).on_param_changed(name, value)
end
end
# Override start to ensure color provider is synchronized
#
# @param start_time: int - Optional start time in milliseconds
# @return self for method chaining
def start(start_time)
# Call parent start method
super(self).start(start_time)
self.color_provider.start(start_time)
return self
end
# String representation
def tostring()
try
return f"RichPaletteAnimation({self.name}, cycle_period={self.cycle_period}, brightness={self.brightness})"
except ..
return "RichPaletteAnimation(uninitialized)"
end
end
end
return {'rich_palette_animation': RichPaletteAnimation}

View File

@ -0,0 +1,292 @@
# Scale animation effect for Berry Animation Framework
#
# This animation scales patterns up or down with configurable scaling factors,
# interpolation methods, and center points.
#@ solidify:ScaleAnimation,weak
class ScaleAnimation : animation.animation
# Non-parameter instance variables only
var scale_phase # Current phase for animated scaling
var source_frame # Frame buffer for source animation
var current_colors # Array of current colors for each pixel
var start_time # Animation start time
# Parameter definitions following parameterized class specification
static var PARAMS = {
"source_animation": {"type": "instance", "default": nil},
"scale_factor": {"min": 1, "max": 255, "default": 128},
"scale_speed": {"min": 0, "max": 255, "default": 0},
"scale_mode": {"min": 0, "max": 3, "default": 0},
"scale_center": {"min": 0, "max": 255, "default": 128},
"interpolation": {"min": 0, "max": 1, "default": 1}
}
# Initialize a new Scale animation
# @param engine: AnimationEngine - Required animation engine
def init(engine)
# Call parent constructor with engine
super(self).init(engine)
# Initialize non-parameter instance variables only
self.scale_phase = 0
self.start_time = self.engine.time_ms
self._initialize_buffers()
end
# Initialize frame buffers based on current strip length
def _initialize_buffers()
var current_strip_length = self.engine.get_strip_length()
self.source_frame = animation.frame_buffer(current_strip_length)
self.current_colors = []
self.current_colors.resize(current_strip_length)
# Initialize colors to black
var i = 0
while i < current_strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
# No special handling needed for most parameters
# Buffers are managed through engine strip length changes
end
# Start/restart the animation
def start(time_ms)
# Call parent start first (handles ValueProvider propagation)
super(self).start(time_ms)
# Reset scale phase for animated modes
self.scale_phase = 0
# Initialize timing
if time_ms == nil
time_ms = self.engine.time_ms
end
self.start_time = time_ms
return self
end
# Update animation state
def update(time_ms)
# Cache parameter values for performance
var current_scale_speed = self.scale_speed
var current_scale_mode = self.scale_mode
var current_source_animation = self.source_animation
# Update scale phase for animated modes
if current_scale_speed > 0 && current_scale_mode > 0
var elapsed = time_ms - self.start_time
# Speed: 0-255 maps to 0-2 cycles per second
var cycles_per_second = tasmota.scale_uint(current_scale_speed, 0, 255, 0, 2)
if cycles_per_second > 0
self.scale_phase = (elapsed * cycles_per_second / 1000) % 256
end
end
# Update source animation if it exists
if current_source_animation != nil
if !current_source_animation.is_running
current_source_animation.start(self.start_time)
end
current_source_animation.update(time_ms)
end
# Calculate scaled colors
self._calculate_scale()
return true
end
# Calculate current scale factor based on mode
def _get_current_scale_factor()
var current_scale_mode = self.scale_mode
var current_scale_factor = self.scale_factor
if current_scale_mode == 0
# Static scale
return current_scale_factor
elif current_scale_mode == 1
# Oscillate between 0.5x and 2.0x
var sine_val = self._sine(self.scale_phase)
return tasmota.scale_uint(sine_val, 0, 255, 64, 255) # 0.5x to 2.0x
elif current_scale_mode == 2
# Grow from 0.5x to 2.0x
return tasmota.scale_uint(self.scale_phase, 0, 255, 64, 255)
else
# Shrink from 2.0x to 0.5x
return tasmota.scale_uint(255 - self.scale_phase, 0, 255, 64, 255)
end
end
# Simple sine approximation
def _sine(angle)
# Simple sine approximation using quarter-wave symmetry
var quarter = angle % 64
if angle < 64
return tasmota.scale_uint(quarter, 0, 64, 128, 255)
elif angle < 128
return tasmota.scale_uint(128 - angle, 0, 64, 128, 255)
elif angle < 192
return tasmota.scale_uint(angle - 128, 0, 64, 128, 0)
else
return tasmota.scale_uint(256 - angle, 0, 64, 128, 0)
end
end
# Calculate scaled colors for all pixels
def _calculate_scale()
# Get current strip length from engine
var current_strip_length = self.engine.get_strip_length()
# Ensure buffers are properly sized
if size(self.current_colors) != current_strip_length
self._initialize_buffers()
end
# Cache parameter values for performance
var current_source_animation = self.source_animation
var current_scale_center = self.scale_center
var current_interpolation = self.interpolation
# Clear source frame
self.source_frame.clear()
# Render source animation to frame
if current_source_animation != nil
current_source_animation.render(self.source_frame, 0)
end
# Get current scale factor
var current_scale = self._get_current_scale_factor()
# Calculate scale center in pixels
var center_pixel = tasmota.scale_uint(current_scale_center, 0, 255, 0, current_strip_length - 1)
# Apply scaling transformation
var i = 0
while i < current_strip_length
# Calculate source position
var distance_from_center = i - center_pixel
# Scale: 128 = 1.0x, 64 = 0.5x, 255 = 2.0x
var scaled_distance = tasmota.scale_uint(distance_from_center * 128, 0, 128 * 128, 0, current_scale * 128) / 128
var source_pos = center_pixel + scaled_distance
if current_interpolation == 0
# Nearest neighbor
if source_pos >= 0 && source_pos < current_strip_length
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
else
self.current_colors[i] = 0xFF000000
end
else
# Linear interpolation using integer math
if source_pos >= 0 && source_pos < current_strip_length - 1
var pos_floor = int(source_pos)
# Use integer fraction (0-255)
var pos_frac_256 = int((source_pos - pos_floor) * 256)
if pos_floor >= 0 && pos_floor < current_strip_length - 1
var color1 = self.source_frame.get_pixel_color(pos_floor)
var color2 = self.source_frame.get_pixel_color(pos_floor + 1)
self.current_colors[i] = self._interpolate_colors(color1, color2, pos_frac_256)
else
self.current_colors[i] = 0xFF000000
end
else
self.current_colors[i] = 0xFF000000
end
end
i += 1
end
end
# Interpolate between two colors using integer math
def _interpolate_colors(color1, color2, factor_256)
if factor_256 <= 0
return color1
elif factor_256 >= 256
return color2
end
# Extract ARGB components
var a1 = (color1 >> 24) & 0xFF
var r1 = (color1 >> 16) & 0xFF
var g1 = (color1 >> 8) & 0xFF
var b1 = color1 & 0xFF
var a2 = (color2 >> 24) & 0xFF
var r2 = (color2 >> 16) & 0xFF
var g2 = (color2 >> 8) & 0xFF
var b2 = color2 & 0xFF
# Interpolate each component using integer math
var a = a1 + ((a2 - a1) * factor_256 / 256)
var r = r1 + ((r2 - r1) * factor_256 / 256)
var g = g1 + ((g2 - g1) * factor_256 / 256)
var b = b1 + ((b2 - b1) * factor_256 / 256)
return (a << 24) | (r << 16) | (g << 8) | b
end
# Render scale to frame buffer
def render(frame, time_ms)
if frame == nil
return false
end
var current_strip_length = self.engine.get_strip_length()
var i = 0
while i < current_strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var mode_names = ["static", "oscillate", "grow", "shrink"]
var current_scale_mode = self.scale_mode
var current_scale_factor = self.scale_factor
var current_scale_speed = self.scale_speed
var mode_name = mode_names[current_scale_mode] != nil ? mode_names[current_scale_mode] : "unknown"
return f"ScaleAnimation({mode_name}, factor={current_scale_factor}, speed={current_scale_speed})"
end
end
# Factory functions following parameterized class specification
# Create a static scale animation preset
def scale_static(engine)
var anim = animation.scale_animation(engine)
anim.scale_mode = 0 # static mode
anim.scale_speed = 0 # no animation
return anim
end
# Create an oscillating scale animation preset
def scale_oscillate(engine)
var anim = animation.scale_animation(engine)
anim.scale_mode = 1 # oscillate mode
anim.scale_speed = 128 # medium speed
return anim
end
# Create a growing scale animation preset
def scale_grow(engine)
var anim = animation.scale_animation(engine)
anim.scale_mode = 2 # grow mode
anim.scale_speed = 128 # medium speed
return anim
end
return {'scale_animation': ScaleAnimation, 'scale_static': scale_static, 'scale_oscillate': scale_oscillate, 'scale_grow': scale_grow}

View File

@ -0,0 +1,212 @@
# Shift animation effect for Berry Animation Framework
#
# This animation shifts/scrolls patterns horizontally across the LED strip
# with configurable speed, direction, and wrapping behavior.
#@ solidify:ShiftAnimation,weak
class ShiftAnimation : animation.animation
# Non-parameter instance variables only
var current_offset # Current shift offset in 1/256th pixels
var source_frame # Frame buffer for source animation
var current_colors # Array of current colors for each pixel
# Parameter definitions with constraints
static var PARAMS = {
"source_animation": {"type": "instance", "default": nil},
"shift_speed": {"min": 0, "max": 255, "default": 128},
"direction": {"min": -1, "max": 1, "default": 1},
"wrap_around": {"type": "bool", "default": true}
}
# Initialize a new Shift animation
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize non-parameter instance variables only
self.current_offset = 0
self._initialize_buffers()
end
# Initialize buffers based on current strip length
def _initialize_buffers()
var current_strip_length = self.engine.get_strip_length()
self.source_frame = animation.frame_buffer(current_strip_length)
self.current_colors = []
self.current_colors.resize(current_strip_length)
# Initialize colors to black
var i = 0
while i < current_strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
# Re-initialize buffers if strip length might have changed
if name == "source_animation"
self._initialize_buffers()
end
end
# Update animation state
def update(time_ms)
super(self).update(time_ms)
# Cache parameter values for performance
var current_shift_speed = self.shift_speed
var current_direction = self.direction
var current_wrap_around = self.wrap_around
var current_source_animation = self.source_animation
var current_strip_length = self.engine.get_strip_length()
# Update shift offset based on speed
if current_shift_speed > 0
var elapsed = time_ms - self.start_time
# Speed: 0-255 maps to 0-10 pixels per second
var pixels_per_second = tasmota.scale_uint(current_shift_speed, 0, 255, 0, 10 * 256)
if pixels_per_second > 0
var total_offset = (elapsed * pixels_per_second / 1000) * current_direction
if current_wrap_around
self.current_offset = total_offset % (current_strip_length * 256)
if self.current_offset < 0
self.current_offset += current_strip_length * 256
end
else
self.current_offset = total_offset
end
end
end
# Update source animation if it exists
if current_source_animation != nil
if !current_source_animation.is_running
current_source_animation.start(self.start_time)
end
current_source_animation.update(time_ms)
end
# Calculate shifted colors
self._calculate_shift()
return true
end
# Calculate shifted colors for all pixels
def _calculate_shift()
# Get current strip length and ensure buffers are correct size
var current_strip_length = self.engine.get_strip_length()
if size(self.current_colors) != current_strip_length
self._initialize_buffers()
end
# Cache parameter values
var current_source_animation = self.source_animation
var current_wrap_around = self.wrap_around
# Clear source frame
self.source_frame.clear()
# Render source animation to frame
if current_source_animation != nil
current_source_animation.render(self.source_frame, 0)
end
# Apply shift transformation
var pixel_offset = self.current_offset / 256 # Convert to pixel units
var sub_pixel_offset = self.current_offset % 256 # Sub-pixel remainder
var i = 0
while i < current_strip_length
var source_pos = i - pixel_offset
if current_wrap_around
# Wrap source position
while source_pos < 0
source_pos += current_strip_length
end
while source_pos >= current_strip_length
source_pos -= current_strip_length
end
# Get color from wrapped position
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
else
# Clamp to strip bounds
if source_pos >= 0 && source_pos < current_strip_length
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
else
self.current_colors[i] = 0xFF000000 # Black for out-of-bounds
end
end
i += 1
end
end
# Render shift to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var current_strip_length = self.engine.get_strip_length()
var i = 0
while i < current_strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var current_direction = self.direction
var current_shift_speed = self.shift_speed
var current_wrap_around = self.wrap_around
var current_priority = self.priority
var dir_str = current_direction > 0 ? "right" : "left"
return f"ShiftAnimation({dir_str}, speed={current_shift_speed}, wrap={current_wrap_around}, priority={current_priority}, running={self.is_running})"
end
end
# Factory functions
# Create a shift animation that scrolls right
def shift_scroll_right(engine)
var anim = animation.shift_animation(engine)
anim.direction = 1
anim.shift_speed = 128
anim.wrap_around = true
return anim
end
# Create a shift animation that scrolls left
def shift_scroll_left(engine)
var anim = animation.shift_animation(engine)
anim.direction = -1
anim.shift_speed = 128
anim.wrap_around = true
return anim
end
# Create a fast scrolling shift animation
def shift_fast_scroll(engine)
var anim = animation.shift_animation(engine)
anim.direction = 1
anim.shift_speed = 200
anim.wrap_around = true
return anim
end
return {
'shift_animation': ShiftAnimation,
'shift_scroll_right': shift_scroll_right,
'shift_scroll_left': shift_scroll_left,
'shift_fast_scroll': shift_fast_scroll
}

View File

@ -0,0 +1,18 @@
# Solid Animation Factory
# Creates a solid color animation using the base Animation class
# Follows the parameterized class specification with engine-only pattern
# Factory function to create a solid animation
# Following the "Engine-only factory functions" pattern from the specification
#
# @param engine: AnimationEngine - Required engine parameter (only parameter)
# @return Animation - A new solid animation instance with default parameters
def solid(engine)
# Create animation with engine-only constructor
var anim = animation.animation(engine)
anim.name = "solid"
return anim
end
return {'solid': solid}

View File

@ -0,0 +1,256 @@
# Sparkle animation effect for Berry Animation Framework
#
# This animation creates random sparkles that appear and fade out over time,
# with configurable density, fade speed, and colors.
#@ solidify:SparkleAnimation,weak
class SparkleAnimation : animation.animation
# Non-parameter instance variables only
var current_colors # Array of current colors for each pixel
var sparkle_states # Array of sparkle states for each pixel
var sparkle_ages # Array of sparkle ages for each pixel
var random_seed # Seed for random number generation
var last_update # Last update time for frame timing
# Parameter definitions following parameterized class specification
static var PARAMS = {
"color": {"default": 0xFFFFFFFF},
"back_color": {"default": 0xFF000000},
"density": {"min": 0, "max": 255, "default": 30},
"fade_speed": {"min": 0, "max": 255, "default": 50},
"sparkle_duration": {"min": 0, "max": 255, "default": 60},
"min_brightness": {"min": 0, "max": 255, "default": 100},
"max_brightness": {"min": 0, "max": 255, "default": 255}
}
# Initialize a new Sparkle animation
# @param engine: AnimationEngine - Required animation engine reference
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize random seed using engine time
self.random_seed = self.engine.time_ms % 65536
# Initialize arrays and state - will be sized when strip length is known
self.current_colors = []
self.sparkle_states = [] # 0 = off, 1-255 = brightness
self.sparkle_ages = [] # Age of each sparkle
self.last_update = 0
# Initialize buffers based on engine strip length
self._initialize_buffers()
end
# Simple pseudo-random number generator
def _random()
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
return self.random_seed
end
# Get random number in range [0, max)
def _random_range(max)
if max <= 0
return 0
end
return self._random() % max
end
# Initialize buffers based on current strip length
def _initialize_buffers()
var current_strip_length = self.engine.get_strip_length()
self.current_colors.resize(current_strip_length)
self.sparkle_states.resize(current_strip_length)
self.sparkle_ages.resize(current_strip_length)
# Initialize all pixels
var back_color = self.back_color
var i = 0
while i < current_strip_length
self.current_colors[i] = back_color
self.sparkle_states[i] = 0
self.sparkle_ages[i] = 0
i += 1
end
end
# Override start method for timing control (acts as both start and restart)
def start(time_ms)
# Call parent start first (handles ValueProvider propagation)
super(self).start(time_ms)
# Reset random seed for consistent restarts
self.random_seed = self.engine.time_ms % 65536
# Reinitialize buffers in case strip length changed
self._initialize_buffers()
return self
end
# Update animation state
def update(time_ms)
super(self).update(time_ms)
# Update at approximately 30 FPS
var update_interval = 33 # ~30 FPS
if time_ms - self.last_update < update_interval
return true
end
self.last_update = time_ms
# Update sparkle simulation
self._update_sparkles(time_ms)
return true
end
# Update sparkle states and create new sparkles
def _update_sparkles(time_ms)
var current_strip_length = self.engine.get_strip_length()
# Cache parameter values for performance
var sparkle_duration = self.sparkle_duration
var fade_speed = self.fade_speed
var density = self.density
var min_brightness = self.min_brightness
var max_brightness = self.max_brightness
var back_color = self.back_color
var i = 0
while i < current_strip_length
# Update existing sparkles
if self.sparkle_states[i] > 0
self.sparkle_ages[i] += 1
# Check if sparkle should fade or die
if self.sparkle_ages[i] >= sparkle_duration
# Sparkle has reached end of life
self.sparkle_states[i] = 0
self.sparkle_ages[i] = 0
self.current_colors[i] = back_color
else
# Fade sparkle based on age and fade speed
var age_ratio = tasmota.scale_uint(self.sparkle_ages[i], 0, sparkle_duration, 0, 255)
var fade_factor = 255 - tasmota.scale_uint(age_ratio, 0, 255, 0, fade_speed)
# Apply fade to brightness
var new_brightness = tasmota.scale_uint(self.sparkle_states[i], 0, 255, 0, fade_factor)
if new_brightness < 10
# Sparkle too dim, turn off
self.sparkle_states[i] = 0
self.sparkle_ages[i] = 0
self.current_colors[i] = back_color
else
# Update sparkle color with new brightness
self._update_sparkle_color(i, new_brightness, time_ms)
end
end
else
# Check if new sparkle should appear
if self._random_range(256) < density
# Create new sparkle
var brightness = min_brightness + self._random_range(max_brightness - min_brightness + 1)
self.sparkle_states[i] = brightness
self.sparkle_ages[i] = 0
self._update_sparkle_color(i, brightness, time_ms)
else
# No sparkle, use background color
self.current_colors[i] = back_color
end
end
i += 1
end
end
# Update color for a specific sparkle
def _update_sparkle_color(pixel, brightness, time_ms)
# Get base color using virtual parameter access
var base_color = 0xFFFFFFFF
# Access color parameter (automatically resolves ValueProviders)
var color_param = self.color
if animation.is_color_provider(color_param) && color_param.get_color_for_value != nil
base_color = color_param.get_color_for_value(brightness, 0)
else
# Use the resolved color value with pixel influence for variation
base_color = self.get_param_value("color", time_ms + pixel * 10)
end
# Apply brightness scaling
var a = (base_color >> 24) & 0xFF
var r = (base_color >> 16) & 0xFF
var g = (base_color >> 8) & 0xFF
var b = base_color & 0xFF
r = tasmota.scale_uint(brightness, 0, 255, 0, r)
g = tasmota.scale_uint(brightness, 0, 255, 0, g)
b = tasmota.scale_uint(brightness, 0, 255, 0, b)
self.current_colors[pixel] = (a << 24) | (r << 16) | (g << 8) | b
end
# Render sparkles to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var current_strip_length = self.engine.get_strip_length()
var i = 0
while i < current_strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var color_param = self.get_param("color")
var color_str
if animation.is_value_provider(color_param)
color_str = str(color_param)
else
color_str = f"0x{self.color :08x}"
end
return f"SparkleAnimation(color={color_str}, density={self.density}, fade_speed={self.fade_speed}, priority={self.priority}, running={self.is_running})"
end
end
# Factory functions following parameterized class specification
# Create a white sparkle animation preset
# @param engine: AnimationEngine - Required animation engine reference
# @return SparkleAnimation - A new white sparkle animation instance
def sparkle_white(engine)
var anim = animation.sparkle_animation(engine)
anim.color = 0xFFFFFFFF # white sparkles
anim.name = "sparkle_white"
return anim
end
# Create a rainbow sparkle animation preset
# @param engine: AnimationEngine - Required animation engine reference
# @return SparkleAnimation - A new rainbow sparkle animation instance
def sparkle_rainbow(engine)
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1 # sine transition
var anim = animation.sparkle_animation(engine)
anim.color = rainbow_provider
anim.name = "sparkle_rainbow"
return anim
end
return {'sparkle_animation': SparkleAnimation, 'sparkle_white': sparkle_white, 'sparkle_rainbow': sparkle_rainbow}

View File

@ -0,0 +1,338 @@
# Twinkle animation effect for Berry Animation Framework
#
# This animation creates a twinkling stars effect with random lights
# appearing and fading at different positions with customizable density and timing.
#@ solidify:TwinkleAnimation,weak
class TwinkleAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system
# Non-parameter instance variables only
var twinkle_states # Array storing twinkle state for each pixel
var current_colors # Array of current colors for each pixel
var last_update # Last update time for timing
var random_seed # Seed for random number generation
# Parameter definitions with constraints
static var PARAMS = {
"color": {"default": 0xFFFFFFFF},
"density": {"min": 0, "max": 255, "default": 128},
"twinkle_speed": {"min": 1, "max": 5000, "default": 6},
"fade_speed": {"min": 0, "max": 255, "default": 180},
"min_brightness": {"min": 0, "max": 255, "default": 32},
"max_brightness": {"min": 0, "max": 255, "default": 255}
}
# Initialize a new Twinkle animation
#
# @param engine: AnimationEngine - The animation engine (REQUIRED)
def init(engine)
# Call parent constructor with engine only
super(self).init(engine)
# Initialize non-parameter instance variables only
self.twinkle_states = []
self.current_colors = []
self.last_update = 0
# Initialize random seed using engine time
self.random_seed = self.engine.time_ms % 65536
# Initialize arrays based on strip length from engine
self._initialize_arrays()
end
# Initialize arrays based on current strip length
def _initialize_arrays()
var strip_length = self.engine.get_strip_length()
# Resize arrays
self.twinkle_states.resize(strip_length)
self.current_colors.resize(strip_length)
# Initialize all pixels to off state
var i = 0
while i < strip_length
self.twinkle_states[i] = 0 # 0 = off, >0 = brightness level
self.current_colors[i] = 0x00000000 # Transparent (alpha = 0)
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "twinkle_speed"
# Handle twinkle_speed - can be Hz (1-20) or period in ms (50-5000)
if value >= 50 # Assume it's period in milliseconds
# Convert period (ms) to frequency (Hz): Hz = 1000 / ms
# Clamp to reasonable range 1-20 Hz
var hz = 1000 / value
if hz < 1
hz = 1
elif hz > 20
hz = 20
end
# Update the parameter with the converted value
self.set_param("twinkle_speed", hz)
end
end
end
# Simple pseudo-random number generator
# Uses a linear congruential generator for consistent results
def _random()
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
return self.random_seed
end
# Get random number in range [0, max)
def _random_range(max)
if max <= 0
return 0
end
return self._random() % max
end
# Update animation state based on current time
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
# Call parent update method first
if !super(self).update(time_ms)
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
# Access parameters via virtual members
var twinkle_speed = self.twinkle_speed
# Check if it's time to update the twinkle simulation
# Update frequency is based on twinkle_speed (Hz)
var update_interval = 1000 / twinkle_speed # milliseconds between updates
if time_ms - self.last_update >= update_interval
self.last_update = time_ms
self._update_twinkle_simulation(time_ms)
end
return true
end
# Update the twinkle simulation with alpha-based fading
def _update_twinkle_simulation(time_ms)
# Access parameters via virtual members (cache for performance)
var fade_speed = self.fade_speed
var density = self.density
var min_brightness = self.min_brightness
var max_brightness = self.max_brightness
var color = self.color
var strip_length = self.engine.get_strip_length()
# Ensure arrays are properly sized
if size(self.twinkle_states) != strip_length
self._initialize_arrays()
end
# Step 1: Fade existing twinkles by reducing alpha
var i = 0
while i < strip_length
var current_color = self.current_colors[i]
var alpha = (current_color >> 24) & 0xFF
if alpha > 0
# Calculate fade amount based on fade_speed
var fade_amount = tasmota.scale_uint(fade_speed, 0, 255, 1, 20)
if alpha <= fade_amount
# Star has faded completely - reset to transparent
self.twinkle_states[i] = 0
self.current_colors[i] = 0x00000000
else
# Reduce alpha while keeping RGB components unchanged
var new_alpha = alpha - fade_amount
var rgb = current_color & 0x00FFFFFF # Keep RGB, clear alpha
self.current_colors[i] = (new_alpha << 24) | rgb
end
end
i += 1
end
# Step 2: Randomly create new twinkles based on density
# For each pixel, check if it should twinkle based on density probability
var j = 0
while j < strip_length
# Only consider pixels that are currently off (transparent)
if self.twinkle_states[j] == 0
# Use density as probability out of 255
if self._random_range(255) < density
# Create new star at full brightness with random intensity alpha
var star_alpha = min_brightness + self._random_range(max_brightness - min_brightness + 1)
# Get base color (automatically resolves ValueProviders)
var base_color = color
# Extract RGB components (ignore original alpha)
var r = (base_color >> 16) & 0xFF
var g = (base_color >> 8) & 0xFF
var b = base_color & 0xFF
# Create new star with full-brightness color and variable alpha
self.twinkle_states[j] = 1 # Mark as active (non-zero)
self.current_colors[j] = (star_alpha << 24) | (r << 16) | (g << 8) | b
end
end
j += 1
end
end
# Render the twinkle to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Use engine time if not provided
if time_ms == nil
time_ms = self.engine.time_ms
end
var strip_length = self.engine.get_strip_length()
# Ensure arrays are properly sized
if size(self.twinkle_states) != strip_length
self._initialize_arrays()
end
# Only render pixels that are actually twinkling (non-transparent)
var modified = false
var i = 0
while i < strip_length
if i < frame.width
var color = self.current_colors[i]
# Only set pixels that have some alpha (are visible)
if (color >> 24) & 0xFF > 0
frame.set_pixel_color(i, color)
modified = true
end
end
i += 1
end
return modified
end
# NO setter/getter methods - use direct assignment instead:
# obj.color = value
# obj.density = value
# obj.twinkle_speed = value
# obj.fade_speed = value
# obj.min_brightness = value
# obj.max_brightness = value
# String representation of the animation
def tostring()
var color_str
var raw_color = self.get_param("color")
if animation.is_value_provider(raw_color)
color_str = str(raw_color)
else
color_str = f"0x{self.color :08x}"
end
return f"TwinkleAnimation(color={color_str}, density={self.density}, twinkle_speed={self.twinkle_speed}, priority={self.priority}, running={self.is_running})"
end
end
# Factory function to create a classic white twinkle animation
#
# @param engine: AnimationEngine - The animation engine
# @return TwinkleAnimation - A new twinkle animation instance
def twinkle_classic(engine)
var anim = animation.twinkle_animation(engine)
anim.color = 0xFFFFFFFF
anim.density = 150
anim.twinkle_speed = 6
anim.fade_speed = 180
anim.min_brightness = 32
anim.max_brightness = 255
return anim
end
# Factory function to create a colored twinkle animation
#
# @param engine: AnimationEngine - The animation engine
# @return TwinkleAnimation - A new twinkle animation instance
def twinkle_solid(engine)
var anim = animation.twinkle_animation(engine)
anim.color = 0xFF0080FF # Blue
anim.density = 100
anim.twinkle_speed = 6
anim.fade_speed = 180
anim.min_brightness = 32
anim.max_brightness = 255
return anim
end
# Factory function to create a rainbow twinkle animation
#
# @param engine: AnimationEngine - The animation engine
# @return TwinkleAnimation - A new twinkle animation instance
def twinkle_rainbow(engine)
var anim = animation.twinkle_animation(engine)
# TODO: Set up rainbow color provider when available
anim.color = 0xFFFFFFFF # White for now
anim.density = 120
anim.twinkle_speed = 6
anim.fade_speed = 180
anim.min_brightness = 32
anim.max_brightness = 255
return anim
end
# Factory function to create a gentle twinkle animation (low density, slow fade)
#
# @param engine: AnimationEngine - The animation engine
# @return TwinkleAnimation - A new twinkle animation instance
def twinkle_gentle(engine)
var anim = animation.twinkle_animation(engine)
anim.color = 0xFFFFD700 # Gold
anim.density = 64
anim.twinkle_speed = 3
anim.fade_speed = 120
anim.min_brightness = 16
anim.max_brightness = 180
return anim
end
# Factory function to create an intense twinkle animation (high density, fast fade)
#
# @param engine: AnimationEngine - The animation engine
# @return TwinkleAnimation - A new twinkle animation instance
def twinkle_intense(engine)
var anim = animation.twinkle_animation(engine)
anim.color = 0xFFFF0000 # Red
anim.density = 200
anim.twinkle_speed = 12
anim.fade_speed = 220
anim.min_brightness = 64
anim.max_brightness = 255
return anim
end
return {
'twinkle_animation': TwinkleAnimation,
'twinkle_classic': twinkle_classic,
'twinkle_solid': twinkle_solid,
'twinkle_rainbow': twinkle_rainbow,
'twinkle_gentle': twinkle_gentle,
'twinkle_intense': twinkle_intense
}

View File

@ -0,0 +1,280 @@
# Wave animation effect for Berry Animation Framework
#
# This animation creates various wave patterns (sine, triangle, square, sawtooth)
# with configurable amplitude, frequency, phase, and movement speed.
#@ solidify:WaveAnimation,weak
class WaveAnimation : animation.animation
# Non-parameter instance variables only
var current_colors # Array of current colors for each pixel
var time_offset # Current time offset for movement
var wave_table # Pre-computed wave table for performance
# Parameter definitions for WaveAnimation
static var PARAMS = {
"color": {"default": 0xFFFF0000},
"back_color": {"default": 0xFF000000},
"wave_type": {"min": 0, "max": 3, "default": 0},
"amplitude": {"min": 0, "max": 255, "default": 128},
"frequency": {"min": 0, "max": 255, "default": 32},
"phase": {"min": 0, "max": 255, "default": 0},
"wave_speed": {"min": 0, "max": 255, "default": 50},
"center_level": {"min": 0, "max": 255, "default": 128}
}
# Initialize a new Wave animation
#
# @param engine: AnimationEngine - The animation engine (required)
def init(engine)
# Call parent constructor
super(self).init(engine)
# Initialize non-parameter instance variables only
self.current_colors = []
self.time_offset = 0
self.wave_table = []
# Initialize wave table for performance
self._init_wave_table()
end
# Initialize wave lookup tables for performance
def _init_wave_table()
self.wave_table.resize(256)
var current_wave_type = self.wave_type
var i = 0
while i < 256
# Generate different wave types
var value = 0
if current_wave_type == 0
# Sine wave - using quarter-wave symmetry
var quarter = i % 64
if i < 64
# First quarter: approximate sine
value = tasmota.scale_uint(quarter, 0, 64, 128, 255)
elif i < 128
# Second quarter: mirror first quarter
value = tasmota.scale_uint(128 - i, 0, 64, 128, 255)
elif i < 192
# Third quarter: negative first quarter
value = tasmota.scale_uint(i - 128, 0, 64, 128, 0)
else
# Fourth quarter: negative second quarter
value = tasmota.scale_uint(256 - i, 0, 64, 128, 0)
end
elif current_wave_type == 1
# Triangle wave
if i < 128
value = tasmota.scale_uint(i, 0, 128, 0, 255)
else
value = tasmota.scale_uint(256 - i, 0, 128, 0, 255)
end
elif current_wave_type == 2
# Square wave
value = i < 128 ? 255 : 0
else
# Sawtooth wave
value = i
end
self.wave_table[i] = value
i += 1
end
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "wave_type"
self._init_wave_table() # Regenerate wave table when wave type changes
end
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Update time offset based on wave speed
var current_wave_speed = self.wave_speed
if current_wave_speed > 0
var elapsed = time_ms - self.start_time
# Speed: 0-255 maps to 0-10 cycles per second
var cycles_per_second = tasmota.scale_uint(current_wave_speed, 0, 255, 0, 10)
if cycles_per_second > 0
self.time_offset = (elapsed * cycles_per_second / 1000) % 256
end
end
# Calculate wave colors
self._calculate_wave(time_ms)
return true
end
# Calculate wave colors for all pixels
def _calculate_wave(time_ms)
var strip_length = self.engine.get_strip_length()
var current_frequency = self.frequency
var current_phase = self.phase
var current_amplitude = self.amplitude
var current_center_level = self.center_level
var current_back_color = self.back_color
var current_color = self.color
# Resize current_colors array if needed
if self.current_colors.size() != strip_length
self.current_colors.resize(strip_length)
end
var i = 0
while i < strip_length
# Calculate wave position for this pixel
var x = tasmota.scale_uint(i, 0, strip_length - 1, 0, 255)
# Apply frequency scaling and phase offset
var wave_pos = ((x * current_frequency / 32) + current_phase + self.time_offset) & 255
# Get wave value from lookup table
var wave_value = self.wave_table[wave_pos]
# Apply amplitude scaling around center level
var scaled_amplitude = tasmota.scale_uint(current_amplitude, 0, 255, 0, 128)
var final_value = 0
if wave_value >= 128
# Upper half of wave
var upper_amount = wave_value - 128
upper_amount = tasmota.scale_uint(upper_amount, 0, 127, 0, scaled_amplitude)
final_value = current_center_level + upper_amount
else
# Lower half of wave
var lower_amount = 128 - wave_value
lower_amount = tasmota.scale_uint(lower_amount, 0, 128, 0, scaled_amplitude)
final_value = current_center_level - lower_amount
end
# Clamp to valid range
if final_value > 255
final_value = 255
elif final_value < 0
final_value = 0
end
# Get color from provider or use background
var color = current_back_color
if final_value > 10 # Threshold to avoid very dim colors
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
color = current_color.get_color_for_value(final_value, 0)
else
# Use resolve_value with wave influence
color = self.resolve_value(current_color, "color", time_ms + final_value * 10)
# Apply wave intensity as brightness scaling
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
r = tasmota.scale_uint(final_value, 0, 255, 0, r)
g = tasmota.scale_uint(final_value, 0, 255, 0, g)
b = tasmota.scale_uint(final_value, 0, 255, 0, b)
color = (a << 24) | (r << 16) | (g << 8) | b
end
end
self.current_colors[i] = color
i += 1
end
end
# Render wave to frame buffer
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var strip_length = self.engine.get_strip_length()
var i = 0
while i < strip_length
if i < frame.width && i < self.current_colors.size()
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# String representation
def tostring()
var wave_names = ["sine", "triangle", "square", "sawtooth"]
var current_wave_type = self.wave_type
var wave_name = wave_names[current_wave_type] != nil ? wave_names[current_wave_type] : "unknown"
var current_color = self.color
var color_str
if animation.is_value_provider(current_color)
color_str = str(current_color)
else
color_str = f"0x{current_color :08x}"
end
return f"WaveAnimation({wave_name}, color={color_str}, freq={self.frequency}, speed={self.wave_speed}, priority={self.priority}, running={self.is_running})"
end
end
# Factory functions
# Create a rainbow sine wave animation
#
# @param engine: AnimationEngine - The animation engine
# @return WaveAnimation - A new wave animation instance
def wave_rainbow_sine(engine)
var anim = animation.wave_animation(engine)
# Set up rainbow color provider
var rainbow_provider = animation.rich_palette(engine)
rainbow_provider.palette = animation.PALETTE_RAINBOW
rainbow_provider.cycle_period = 5000
rainbow_provider.transition_type = 1 # sine transition
rainbow_provider.brightness = 255
rainbow_provider.set_range(0, 255)
anim.color = rainbow_provider
anim.wave_type = 0 # sine wave
anim.frequency = 32
anim.wave_speed = 50
return anim
end
# Create a single color sine wave animation
#
# @param engine: AnimationEngine - The animation engine
# @return WaveAnimation - A new wave animation instance
def wave_single_sine(engine)
var anim = animation.wave_animation(engine)
anim.color = 0xFFFF0000 # Default red color
anim.wave_type = 0 # sine wave
anim.frequency = 32
anim.wave_speed = 50
return anim
end
# Create a custom wave animation
#
# @param engine: AnimationEngine - The animation engine
# @return WaveAnimation - A new wave animation instance
def wave_custom(engine)
var anim = animation.wave_animation(engine)
anim.color = 0xFFFFFF00 # Default yellow color
anim.wave_type = 2 # square wave
anim.frequency = 40
anim.wave_speed = 30
return anim
end
return {'wave_animation': WaveAnimation, 'wave_rainbow_sine': wave_rainbow_sine, 'wave_single_sine': wave_single_sine, 'wave_custom': wave_custom}

View File

@ -27,4 +27,7 @@
#include "be_mapping.h"
#include "solidify/solidified_animation.h"
#ifdef USE_BERRY_ANIMATION_DSL
#include "solidify/solidified_animation_dsl.h"
#endif
#endif

View File

@ -1,104 +1,125 @@
# Animation base class
# Defines the interface for all animations in the Berry Animation Framework
#
# Animation extends Pattern with temporal behavior - duration, looping, timing, etc.
# This allows animations to be used anywhere patterns can be used, but with
# additional time-based capabilities.
# Animation base class - The unified root of the animation hierarchy
#
# An Animation defines WHAT should be displayed and HOW it changes over time.
# Animations can generate colors for any pixel at any time, have priority for layering,
# and can be rendered directly. They also support temporal behavior like duration and looping.
#
# This is the unified base class for all visual elements in the framework.
# A Pattern is simply an Animation with infinite duration (duration = 0).
class Animation : animation.pattern
# Animation-specific state variables (inherits priority, opacity, name, etc. from Pattern)
class Animation : animation.parameterized_object
# Non-parameter instance variables only
var start_time # Time when animation started (ms) (int)
var current_time # Current animation time (ms) (int)
var duration # Total animation duration, 0 for infinite (ms) (int)
var loop # Whether animation should loop (bool)
# Parameter definitions
static var PARAMS = {
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
"is_running": {"type": "bool", "default": false}, # Whether the animation is active
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
"loop": {"type": "bool", "default": true}, # Whether to loop when duration is reached
"opacity": {"min": 0, "max": 255, "default": 255}, # Animation opacity/brightness (0-255)
"color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB)
}
# Initialize a new animation
#
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
# @param duration: int - Duration in milliseconds, defaults to 0 (infinite) if nil
# @param loop: bool - Whether animation should loop when duration is reached, defaults to false if nil
# @param opacity: int - Animation opacity (0-255), defaults to 255 if nil
# @param name: string - Optional name for the animation, defaults to "animation" if nil
def init(priority, duration, loop, opacity, name)
# Call parent Pattern constructor
super(self).init(priority, opacity, name != nil ? name : "animation")
# @param engine: AnimationEngine - Reference to the animation engine (required)
def init(engine)
# Initialize parameter system with engine
super(self).init(engine)
# Initialize animation-specific properties
# Initialize non-parameter instance variables
self.start_time = 0
self.current_time = 0
self.duration = duration != nil ? duration : 0 # default infinite
self.loop = loop != nil ? (loop ? 1 : 0) : 0
# Register animation-specific parameters
self._register_param("duration", {"min": 0, "default": 0})
self._register_param("loop", {"min": 0, "max": 1, "default": 0})
# Set initial values for animation parameters
self.set_param("duration", self.duration)
self.set_param("loop", self.loop)
end
# Start the animation (override Pattern's start to add timing)
# Start/restart the animation (make it active and reset timing)
#
# @param start_time: int - Optional start time in milliseconds
# @return self for method chaining
def start(start_time)
if !self.is_running
super(self).start() # Call Pattern's start method
self.start_time = start_time != nil ? start_time : tasmota.millis()
self.current_time = self.start_time
end
return self
end
# Pause the animation (keeps state but doesn't update)
#
# @return self for method chaining
def pause()
self.is_running = false
return self
end
# Resume the animation from where it was paused
#
# @return self for method chaining
def resume()
self.is_running = true
return self
end
# Reset the animation to its initial state
#
# @return self for method chaining
def reset()
self.start_time = tasmota.millis()
# Set is_running directly in values map to avoid infinite loop
self.values["is_running"] = true
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
self.start_time = actual_start_time
self.current_time = self.start_time
# Start/restart all value providers in parameters
self._start_value_providers(actual_start_time)
return self
end
# Update animation state based on current time (override Pattern's update)
# Helper method to start/restart all value providers in parameters
#
# @param time_ms: int - Time to pass to value provider start methods
def _start_value_providers(time_ms)
# Iterate through all parameter values
for param_value : self.values
# Check if the parameter value is a value provider
if animation.is_value_provider(param_value)
# Call start method if it exists (acts as restart)
try
param_value.start(time_ms)
except .. as e
# Ignore errors if start method doesn't exist or fails
end
end
end
end
# Handle parameter changes - specifically for is_running to control start/stop
#
# @param name: string - Parameter name that changed
# @param value: any - New parameter value
def on_param_changed(name, value)
if name == "is_running"
if value == true
# Start the animation (but avoid infinite loop by not setting is_running again)
var actual_start_time = self.engine.time_ms
self.start_time = actual_start_time
self.current_time = self.start_time
# Start/restart all value providers in parameters
self._start_value_providers(actual_start_time)
elif value == false
# Stop the animation - just set the internal state
# (is_running is already set to false by the parameter system)
end
end
end
# Update animation state based on current time
# This method should be called regularly by the animation controller
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if animation is still running, false if completed
def update(time_ms)
if !self.is_running
# Access is_running via virtual member
var current_is_running = self.is_running
if !current_is_running
return false
end
self.current_time = time_ms
var elapsed = self.current_time - self.start_time
# Access parameters via virtual members
var current_duration = self.duration
var current_loop = self.loop
# Check if animation has completed its duration
if self.duration > 0 && elapsed >= self.duration
if self.loop
if current_duration > 0 && elapsed >= current_duration
if current_loop
# Reset start time to create a looping effect
# We calculate the precise new start time to avoid drift
var loops_completed = elapsed / self.duration
self.start_time = self.start_time + (loops_completed * self.duration)
var loops_completed = elapsed / current_duration
self.start_time = self.start_time + (loops_completed * current_duration)
else
# Animation completed, stop it
self.stop()
# Animation completed, make it inactive
# Set directly in values map to avoid triggering on_param_changed
self.values["is_running"] = false
return false
end
end
@ -106,55 +127,75 @@ class Animation : animation.pattern
return true
end
# Get the normalized progress of the animation (0 to 255)
#
# @return int - Progress from 0 (start) to 255 (end)
def get_progress()
if self.duration <= 0
return 0 # Infinite animations always return 0 progress
end
var elapsed = self.current_time - self.start_time
var progress = elapsed % self.duration # Handle looping
# For non-looping animations, if we've reached exactly the duration,
# return maximum progress instead of 0 (which would be the modulo result)
if !self.loop && elapsed >= self.duration
return 255
end
return tasmota.scale_uint(progress, 0, self.duration, 0, 255)
end
# Set the animation duration
#
# @param duration: int - New duration in milliseconds
# @return self for method chaining
def set_duration(duration)
self.set_param("duration", duration)
return self
end
# Set whether the animation should loop
#
# @param loop: bool - Whether to loop the animation
# @return self for method chaining
def set_loop(loop)
self.set_param("loop", int(loop))
return self
end
# Render the animation to the provided frame buffer
# Animations can override this, but they inherit the base render method from Pattern
# Default implementation renders a solid color (makes Animation equivalent to solid pattern)
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Current time in milliseconds
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
# Call parent Pattern render method
return super(self).render(frame, time_ms)
# Access is_running via virtual member
var current_is_running = self.is_running
if !current_is_running || frame == nil
return false
end
# Update animation state
self.update(time_ms)
# Access parameters via virtual members (auto-resolves ValueProviders)
var current_color = self.color
var current_opacity = self.opacity
# Fill the entire frame with the current color
frame.fill_pixels(current_color)
# Apply resolved opacity if not full
if current_opacity < 255
frame.apply_brightness(current_opacity)
end
return true
end
# Get a color for a specific pixel position and time
# Default implementation returns the animation's color (solid color for all pixels)
#
# @param pixel: int - Pixel index (0-based)
# @param time_ms: int - Current time in milliseconds
# @return int - Color in ARGB format (0xAARRGGBB)
def get_color_at(pixel, time_ms)
return self.get_param_value("color", time_ms)
end
# Get a color based on time (convenience method)
#
# @param time_ms: int - Current time in milliseconds
# @return int - Color in ARGB format (0xAARRGGBB)
def get_color(time_ms)
return self.get_color_at(0, time_ms)
end
# Get the normalized progress of the animation (0 to 255)
#
# @return int - Progress from 0 (start) to 255 (end)
def get_progress()
var current_duration = self.duration
if current_duration <= 0
return 0 # Infinite animations always return 0 progress
end
var elapsed = self.current_time - self.start_time
var progress = elapsed % current_duration # Handle looping
# For non-looping animations, if we've reached exactly the duration,
# return maximum progress instead of 0 (which would be the modulo result)
var current_loop = self.loop
if !current_loop && elapsed >= current_duration
return 255
end
return tasmota.scale_uint(progress, 0, current_duration, 0, 255)
end
# String representation of the animation

View File

@ -16,6 +16,7 @@ class AnimationEngine
# State management
var is_running # Whether engine is active
var last_update # Last update time in milliseconds
var time_ms # Current time in milliseconds (updated each frame)
var fast_loop_closure # Stored closure for fast_loop registration
# Performance optimization
@ -39,6 +40,7 @@ class AnimationEngine
# Initialize state
self.is_running = false
self.last_update = 0
self.time_ms = 0
self.fast_loop_closure = nil
self.render_needed = false
end
@ -166,6 +168,9 @@ class AnimationEngine
current_time = tasmota.millis()
end
# Update engine time
self.time_ms = current_time
# Throttle updates to ~5ms intervals
var delta_time = current_time - self.last_update
if delta_time < 5
@ -350,6 +355,10 @@ class AnimationEngine
return self.strip
end
def get_strip_length()
return self.width
end
def is_active()
return self.is_running
end

View File

@ -0,0 +1,401 @@
# ParameterizedObject - Base class for parameter management
#
# This class provides a common parameter management system that can be shared
# between Animation and ValueProvider classes. It handles parameter validation,
# storage, and retrieval with support for ValueProvider instances.
#
# Parameters are stored in a 'values' map and accessed via virtual instance variables
# through member() and setmember() methods. Subclasses should not declare instance
# variables for parameters, but use the PARAMS system only.
class ParameterizedObject
var values # Map storing all parameter values
var engine # Reference to the animation engine
# Static parameter definitions - should be overridden by subclasses
static var PARAMS = {}
# Initialize parameter system
#
# @param engine: AnimationEngine - Reference to the animation engine (required)
def init(engine)
if engine == nil || type(engine) != "instance"
raise "value_error", "ParameterizedObject requires an engine parameter"
end
self.engine = engine
self.values = {}
self._init_parameter_values()
end
# Private method to initialize parameter values from the class hierarchy
def _init_parameter_values()
import introspect
# Walk up the class hierarchy to initialize parameters with defaults
var current_class = classof(self)
while current_class != nil
# Check if this class has PARAMS
if introspect.contains(current_class, "PARAMS")
var class_params = current_class.PARAMS
# Initialize parameters from this class with their default values
for param_name : class_params.keys()
# Only set if not already set (child class defaults take precedence)
if !self.values.contains(param_name)
var param_def = class_params[param_name]
if param_def.contains("default")
self.values[param_name] = param_def["default"]
end
end
end
end
# Move to parent class
current_class = super(current_class)
end
end
# Private method to check if a parameter exists in the class hierarchy
#
# @param name: string - Parameter name to check
# @return bool - True if parameter exists in any class in the hierarchy
def _has_param(name)
import introspect
# Walk up the class hierarchy to find the parameter
var current_class = classof(self)
while current_class != nil
# Check if this class has PARAMS
if introspect.contains(current_class, "PARAMS")
var class_params = current_class.PARAMS
if class_params.contains(name)
return true
end
end
# Move to parent class
current_class = super(current_class)
end
return false
end
# Private method to get parameter definition from the class hierarchy
#
# @param name: string - Parameter name
# @return map - Parameter definition or nil if not found
def _get_param_def(name)
import introspect
# Walk up the class hierarchy to find the parameter definition
var current_class = classof(self)
while current_class != nil
# Check if this class has PARAMS
if introspect.contains(current_class, "PARAMS")
var class_params = current_class.PARAMS
if class_params.contains(name)
return class_params[name]
end
end
# Move to parent class
current_class = super(current_class)
end
return nil
end
# Virtual member access - allows obj.param_name syntax
# This is called when accessing a member that doesn't exist as a real instance variable
#
# @param name: string - Parameter name being accessed
# @return any - Resolved parameter value (ValueProvider resolved to actual value)
def member(name)
# Check if it's a parameter (either set in values or defined in PARAMS)
if self.values.contains(name) || self._has_param(name)
return self._resolve_parameter_value(name, self.engine.time_ms)
end
# Not a parameter, raise attribute error (consistent with setmember behavior)
raise "attribute_error", f"'{classname(self)}' object has no attribute '{name}'"
end
# Virtual member assignment - allows obj.param_name = value syntax
# This is called when setting a member that doesn't exist as a real instance variable
#
# @param name: string - Parameter name being set
# @param value: any - Value to set (can be static value or ValueProvider)
def setmember(name, value)
# Check if it's a parameter in the class hierarchy and set it with validation
if self._has_param(name)
self._set_parameter_value(name, value)
else
# Not a parameter, this will cause an error in normal Berry behavior
raise "attribute_error", f"'{classname(self)}' object has no attribute '{name}'"
end
end
# Internal method to set a parameter value with validation
#
# @param name: string - Parameter name
# @param value: any - Value to set (can be static value or ValueProvider)
def _set_parameter_value(name, value)
# Validate the value (skip validation for ValueProvider instances)
if !animation.is_value_provider(value)
self._validate_param(name, value) # This will raise exception with details if invalid
end
# Store the value
self.values[name] = value
# Notify of parameter change
self.on_param_changed(name, value)
end
# Internal method to resolve a parameter value (handles ValueProviders)
#
# @param name: string - Parameter name
# @param time_ms: int - Current time in milliseconds for ValueProvider resolution
# @return any - Resolved value (static or from ValueProvider)
def _resolve_parameter_value(name, time_ms)
if !self.values.contains(name)
# Return default if available from class hierarchy
var param_def = self._get_param_def(name)
if param_def != nil && param_def.contains("default")
return param_def["default"]
end
return nil
end
var value = self.values[name]
# If it's a ValueProvider, resolve it using produce_value
if animation.is_value_provider(value)
return value.produce_value(name, time_ms)
else
# It's a static value, return as-is
return value
end
end
# Validate a parameter value against its constraints
# Raises detailed exceptions for validation failures
#
# @param name: string - Parameter name
# @param value: any - Value to validate
def _validate_param(name, value)
var constraints = self._get_param_def(name)
if constraints == nil
raise "value_error", f"Parameter '{name}' is not defined for class '{classname(self)}'"
end
# Accept ValueProvider instances for all parameters
if animation.is_value_provider(value)
return
end
# Handle nil values
if value == nil
# Check if nil is explicitly allowed via nillable attribute
if constraints.contains("nillable") && constraints["nillable"] == true
return # nil is allowed for this parameter
end
# Check if there's a default value (nil is acceptable if there's a default)
if constraints.contains("default")
return # nil is acceptable, will use default
end
# nil is not allowed for this parameter
raise "value_error", f"Parameter '{name}' does not accept nil values"
end
# Type validation - default type is "int" if not specified
var expected_type = "int" # Default type
if constraints.contains("type")
expected_type = constraints["type"]
end
# Get actual type for validation
var actual_type = type(value)
# Skip type validation if expected type is "any"
if expected_type != "any"
# Validate type
if expected_type != actual_type
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
end
end
# Range validation for integer values only
if actual_type == "int"
if constraints.contains("min") && value < constraints["min"]
raise "value_error", f"Parameter '{name}' value {value} is below minimum {constraints['min']}"
end
if constraints.contains("max") && value > constraints["max"]
raise "value_error", f"Parameter '{name}' value {value} is above maximum {constraints['max']}"
end
end
# Enum validation
if constraints.contains("enum")
var valid = false
import introspect
var enum_list = constraints["enum"]
var list_size = enum_list.size()
var i = 0
while (i < list_size)
var enum_value = enum_list[i]
if value == enum_value
valid = true
break
end
i += 1
end
if !valid
raise "value_error", f"Parameter '{name}' value {value} is not in allowed values {enum_list}"
end
end
end
# Set a parameter value with validation
#
# @param name: string - Parameter name
# @param value: any - Value to set
# @return bool - True if parameter was set, false if validation failed
def set_param(name, value)
# Check if parameter exists in class hierarchy
if !self._has_param(name)
return false
end
try
self._set_parameter_value(name, value)
return true
except "value_error" as e
# Validation failed - return false for method-based setting
return false
end
end
# Get a parameter value (returns raw stored value, not resolved)
#
# @param name: string - Parameter name
# @param default_value: any - Default value if parameter not found
# @return any - Parameter value or default (may be ValueProvider)
def get_param(name, default_value)
# Check stored values
if self.values.contains(name)
return self.values[name]
end
# Fall back to parameter default from class hierarchy
var param_def = self._get_param_def(name)
if param_def != nil && param_def.contains("default")
return param_def["default"]
end
return default_value
end
# Helper method to resolve a value that can be either static or from a value provider
#
# @param value: any - Static value or value provider instance
# @param param_name: string - Parameter name for specific produce_value() method lookup
# @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider)
def resolve_value(value, param_name, time_ms)
if value == nil
return nil
end
if animation.is_value_provider(value)
return value.produce_value(param_name, time_ms)
else
return value
end
end
# Get parameter metadata
#
# @param name: string - Parameter name
# @return map - Parameter metadata or nil if not found
def get_param_metadata(name)
return self._get_param_def(name)
end
# Get all parameter metadata from class hierarchy
#
# @return map - Map of all parameter metadata
def get_params_metadata()
import introspect
var all_params = {}
# Walk up the class hierarchy to collect all parameter definitions
var current_class = classof(self)
while current_class != nil
# Check if this class has PARAMS
if introspect.contains(current_class, "PARAMS")
var class_params = current_class.PARAMS
# Add parameters from this class (child class parameters override parent)
for param_name : class_params.keys()
if !all_params.contains(param_name) # Don't override child class params
all_params[param_name] = class_params[param_name]
end
end
end
# Move to parent class
current_class = super(current_class)
end
return all_params
end
# Helper method to get a resolved value from either a static value or a value provider
# This is the same as accessing obj.param_name but with explicit time
#
# @param param_name: string - Name of the parameter
# @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider)
def get_param_value(param_name, time_ms)
return self._resolve_parameter_value(param_name, time_ms)
end
# Start the object - placeholder for future implementation
#
# @return self for method chaining
def start(time_ms)
return self
end
# Method called when a parameter is changed
# Subclasses should override this to handle parameter changes
#
# @param name: string - Parameter name
# @param value: any - New parameter value
def on_param_changed(name, value)
# Default implementation does nothing
end
# Equality operator for object identity comparison
# This prevents the member() method from being called during == comparisons
#
# @param other: any - Object to compare with
# @return bool - True if objects are the same instance
def ==(other)
import introspect
return introspect.toptr(self) == introspect.toptr(other)
end
# Inequality operator for object identity comparison
# This prevents the member() method from being called during != comparisons
#
# @param other: any - Object to compare with
# @return bool - True if objects are different instances
def !=(other)
return !(self == other)
end
end
return {'parameterized_object': ParameterizedObject}

View File

@ -1,381 +0,0 @@
# Pattern base class - The root of the animation hierarchy
#
# A Pattern defines WHAT should be displayed - it can generate colors for any pixel
# at any time. Patterns have priority for layering and can be rendered directly.
#
# This is the base class for both simple patterns and complex animations.
#@ solidify:Pattern,weak
class Pattern
# Core pattern properties
var priority # Rendering priority (higher = on top) (int)
var opacity # Pattern opacity (0-255) (int)
var name # Optional name for the pattern (string)
var is_running # Whether the pattern is active (bool)
var params # Map of pattern parameters with their constraints (map)
var param_values # Map of current parameter values (map)
# Initialize a new pattern
#
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
# @param opacity: int - Pattern opacity (0-255), defaults to 255 if nil
# @param name: string - Optional name for the pattern, defaults to "pattern" if nil
def init(priority, opacity, name)
self.priority = priority != nil ? priority : 10
self.opacity = opacity != nil ? opacity : 255
self.name = name != nil ? name : "pattern"
self.is_running = false
self.params = {}
self.param_values = {}
# Register common parameters with validation
self._register_param("priority", {"min": 0, "default": 10})
self._register_param("opacity", {"min": 0, "max": 255, "default": 255})
# Set initial values for common parameters
self.set_param("priority", self.priority)
self.set_param("opacity", self.opacity)
end
# Start the pattern (make it active)
#
# @return self for method chaining
def start()
self.is_running = true
return self
end
# Stop the pattern (make it inactive)
#
# @return self for method chaining
def stop()
self.is_running = false
return self
end
# Update pattern state based on current time
# Base patterns are typically stateless, but this allows for time-varying patterns
#
# @param time_ms: int - Current time in milliseconds
# @return bool - True if pattern is still active, false if completed
def update(time_ms)
return self.is_running
end
# Render the pattern to the provided frame buffer
# This is an abstract method that must be implemented by subclasses
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Current time in milliseconds
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
# This is an abstract method that should be overridden by subclasses
# The base implementation does nothing
return false
end
# Get a color for a specific pixel position and time
# This is the core method that defines what a pattern looks like
#
# @param pixel: int - Pixel index (0-based)
# @param time_ms: int - Current time in milliseconds
# @return int - Color in ARGB format (0xAARRGGBB)
def get_color_at(pixel, time_ms)
# Base implementation returns white
# Subclasses should override this to provide actual pattern logic
return 0xFFFFFFFF
end
# Set the pattern priority
#
# @param priority: int - New priority value
# @return self for method chaining
def set_priority(priority)
self.set_param("priority", priority)
return self
end
# Set the pattern opacity
#
# @param opacity: int - New opacity value (0-255)
# @return self for method chaining
def set_opacity(opacity)
self.set_param("opacity", opacity)
return self
end
# Register a parameter with validation constraints
#
# @param name: string - Parameter name
# @param constraints: map - Validation constraints for the parameter
# @return self for method chaining
def _register_param(name, constraints)
if constraints == nil
constraints = {}
end
self.params[name] = constraints
return self
end
# Register a new parameter with validation constraints
#
# @param name: string - Parameter name
# @param constraints: map - Validation constraints for the parameter
# @return self for method chaining
def register_param(name, constraints)
return self._register_param(name, constraints)
end
# Validate a parameter value against its constraints
#
# @param name: string - Parameter name
# @param value: any - Value to validate
# @return bool - True if valid, false otherwise
def _validate_param(name, value)
if !self.params.contains(name)
return false # Parameter not registered
end
var constraints = self.params[name]
# Check if value is nil and there's a default
if value == nil && constraints.contains("default")
value = constraints["default"]
end
# Accept ValueProvider instances for all parameters
if animation.is_value_provider(value)
return true
end
# Only accept integer values
if type(value) != "int"
return false
end
# Range validation for integer values
if constraints.contains("min") && value < constraints["min"]
return false
end
if constraints.contains("max") && value > constraints["max"]
return false
end
# Enum validation
if constraints.contains("enum")
var valid = false
import introspect
var enum_list = constraints["enum"]
var list_size = enum_list.size()
var i = 0
while (i < list_size)
var enum_value = enum_list[i]
if value == enum_value
valid = true
break
end
i += 1
end
if !valid
return false
end
end
return true
end
# Set a parameter value with validation
#
# @param name: string - Parameter name
# @param value: any - Value to set
# @return bool - True if parameter was set, false if validation failed
def set_param(name, value)
import introspect
# Check if parameter exists
if !self.params.contains(name)
return false
end
# Validate the value
if !animation.is_value_provider(value)
if !self._validate_param(name, value)
return false
end
if introspect.contains(self, name)
self.(name) = value
else
self.param_values[name] = value
end
self.on_param_changed(name, value)
else
if introspect.contains(self, name)
self.(name) = value
else
self.param_values[name] = value
end
end
return true
end
# Get a parameter value
#
# @param name: string - Parameter name
# @param default_value: any - Default value if parameter not found
# @return any - Parameter value or default
def get_param(name, default_value)
import introspect
var method_name = "get_" + name
if introspect.contains(self, method_name)
var method = self.(method_name)
return method(self) # since it's not a method call, we need to pass the self as first parameter
end
if self.param_values.contains(name)
return self.param_values[name]
end
if introspect.contains(self, name)
return self.(name)
end
if self.params.contains(name) && self.params[name].contains("default")
return self.params[name]["default"]
end
return default_value
end
# Helper method to resolve a value that can be either static or from a value provider
#
# @param value: any - Static value or value provider instance
# @param param_name: string - Parameter name for specific get_XXX() method lookup
# @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider)
def resolve_value(value, param_name, time_ms)
if value == nil
return nil
end
if animation.is_value_provider(value)
if animation.is_color_provider(value)
return value.get_color(time_ms)
end
var method_name = "get_" + param_name
import introspect
var method = introspect.get(value, method_name)
if type(method) == "function"
return method(value, time_ms)
else
return value.get_value(time_ms)
end
else
return value
end
end
# Get parameter metadata
#
# @param name: string - Parameter name
# @return map - Parameter metadata or nil if not found
def get_param_metadata(name)
if self.params.contains(name)
return self.params[name]
end
return nil
end
# Get all parameter metadata
#
# @return map - Map of all parameter metadata
def get_params_metadata()
return self.params
end
# Get all parameter values
#
# @return map - Map of all parameter values
def get_params()
return self.param_values
end
# Helper method to get a value from either a static value or a value provider
# This method checks if the parameter contains a value provider instance,
# and if so, calls the appropriate get_XXX() method on it.
#
# @param param_name: string - Name of the parameter
# @param time_ms: int - Current time in milliseconds
# @return any - The resolved value (static or from provider)
def get_param_value(param_name, time_ms)
var param_value = self.get_param(param_name, nil)
if param_value == nil
return nil
end
# Check if it's a value provider instance
if animation.is_value_provider(param_value)
# Check for ColorProvider first for optimal color handling
if animation.is_color_provider(param_value)
return param_value.get_color(time_ms)
end
# Try to call the specific get_XXX method for this parameter
var method_name = "get_" + param_name
# Use introspect to check if the method exists
import introspect
var method = introspect.get(param_value, method_name)
if type(method) == "function"
# Call the specific method (e.g., get_pulse_size())
return method(param_value, time_ms) # Pass the instance as first argument (self)
else
# Fall back to generic get_value method
return param_value.get_value(time_ms)
end
else
# It's a static value, return as-is
return param_value
end
end
# Helper method to set a parameter that can be either a static value or a value provider
# This method automatically wraps static values in a StaticValueProvider if needed
#
# @param param_name: string - Name of the parameter
# @param value: any - Static value or value provider instance
# @return bool - True if parameter was set successfully
def set_param_value(param_name, value)
# If it's already a value provider, use it directly
if animation.is_value_provider(value)
return self.set_param(param_name, value)
else
# It's a static value, wrap it in a StaticValueProvider
var static_provider = animation.static_value_provider(value)
return self.set_param(param_name, static_provider)
end
end
# Method called when a parameter is changed
# Subclasses should override this to handle parameter changes
#
# @param name: string - Parameter name
# @param value: any - New parameter value
def on_param_changed(name, value)
# Base implementation does nothing
end
# String representation of the pattern
def tostring()
return f"Pattern({self.name}, priority={self.priority}, opacity={self.opacity}, running={self.is_running})"
end
end
return {'pattern': Pattern}

View File

@ -26,7 +26,7 @@ class SequenceManager
self.steps = steps
self.step_index = 0
self.step_start_time = tasmota.millis()
self.step_start_time = self.controller.time_ms
self.is_running = true
if size(self.steps) > 0
@ -48,7 +48,7 @@ class SequenceManager
return
end
var current_time = tasmota.millis()
var current_time = self.controller.time_ms
var current_step = self.steps[self.step_index]
# Check if current step has completed
@ -80,7 +80,7 @@ class SequenceManager
# Set duration if specified
if step.contains("duration") && step["duration"] > 0
anim.set_duration(step["duration"])
anim.duration = step["duration"]
end
elif step["type"] == "wait"
@ -89,11 +89,10 @@ class SequenceManager
elif step["type"] == "stop"
var anim = step["animation"]
anim.stop()
self.controller.remove_animation(anim)
end
self.step_start_time = tasmota.millis()
self.step_start_time = self.controller.time_ms
end
# Advance to the next step in the sequence
@ -102,7 +101,6 @@ class SequenceManager
var current_step = self.steps[self.step_index]
if current_step["type"] == "play" && current_step.contains("duration")
var anim = current_step["animation"]
anim.stop()
self.controller.remove_animation(anim)
end
@ -131,7 +129,7 @@ class SequenceManager
"step_index": self.step_index,
"total_steps": size(self.steps),
"current_step": self.steps[self.step_index],
"elapsed_ms": tasmota.millis() - self.step_start_time
"elapsed_ms": self.controller.time_ms - self.step_start_time
}
end
end

View File

@ -1,750 +0,0 @@
# API Reference
Complete reference for the Tasmota Berry Animation Framework API.
## Core Classes
### AnimationEngine
The central controller for all animations.
```berry
var engine = animation.create_engine(strip)
```
#### Methods
**`add_animation(animation)`**
- Adds an animation to the engine
- Auto-starts the animation if engine is running
- Returns: `self` (for method chaining)
**`remove_animation(animation)`**
- Removes an animation from the engine
- Returns: `self`
**`clear()`**
- Removes all animations
- Returns: `self`
**`start()`**
- Starts the engine and all animations
- Integrates with Tasmota's `fast_loop`
- Returns: `self`
**`stop()`**
- Stops the engine and all animations
- Returns: `self`
**`size()`**
- Returns: Number of active animations
**`is_active()`**
- Returns: `true` if engine is running
#### Example
```berry
var strip = Leds(30)
var engine = animation.create_engine(strip)
var pulse = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
engine.add_animation(pulse).start()
```
### Pattern (Base Class)
Base class for all visual elements.
#### Properties
- **`priority`** (int) - Rendering priority (higher = on top)
- **`opacity`** (int) - Opacity 0-255 for blending
- **`name`** (string) - Pattern identification
- **`is_running`** (bool) - Whether pattern is active
#### Methods
**`start()`** / **`stop()`**
- Control pattern lifecycle
- Returns: `self`
**`set_priority(priority)`**
- Set rendering priority
- Returns: `self`
**`set_opacity(opacity)`**
- Set opacity (0-255)
- Returns: `self`
### Animation (Extends Pattern)
Adds temporal behavior to patterns.
#### Additional Properties
- **`duration`** (int) - Animation duration in ms (0 = infinite)
- **`loop`** (bool) - Whether to loop when complete
- **`start_time`** (int) - When animation started
- **`current_time`** (int) - Current animation time
#### Additional Methods
**`set_duration(duration_ms)`**
- Set animation duration
- Returns: `self`
**`set_loop(loop)`**
- Enable/disable looping
- Returns: `self`
**`get_progress()`**
- Returns: Animation progress (0-255)
## Animation Functions
### Basic Animations
**`animation.solid(color, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates solid color animation
- **color**: ARGB color value (0xAARRGGBB) or ValueProvider instance
- Returns: `PatternAnimation` instance
```berry
var red = animation.solid(0xFFFF0000)
var blue = animation.solid(0xFF0000FF, 10, 5000, true, 200, "blue_anim")
var dynamic = animation.solid(animation.smooth(0xFF000000, 0xFFFFFFFF, 3000))
```
**`animation.pulse(pattern, period_ms, min_brightness=0, max_brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates pulsing animation
- **pattern**: Base pattern to pulse
- **period_ms**: Pulse period in milliseconds
- **min_brightness**: Minimum brightness (0-255)
- **max_brightness**: Maximum brightness (0-255)
- Returns: `PulseAnimation` instance
```berry
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
```
**`animation.breathe(color, period_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates smooth breathing effect
- **color**: ARGB color value or ValueProvider instance
- **period_ms**: Breathing period in milliseconds
- Returns: `BreatheAnimation` instance
```berry
var breathe_blue = animation.breathe(0xFF0000FF, 4000)
var dynamic_breathe = animation.breathe(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00], 2000), 4000)
```
### Palette-Based Animations
**`animation.rich_palette_animation(palette, period_ms, transition_type=1, brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates palette-based color cycling animation
- **palette**: Palette in VRGB bytes format or palette name
- **period_ms**: Cycle period in milliseconds
- **transition_type**: 0=linear, 1=smooth (sine)
- **brightness**: Overall brightness (0-255)
- Returns: `FilledAnimation` instance
```berry
var rainbow = animation.rich_palette_animation(animation.PALETTE_RAINBOW, 5000, 1, 255)
```
### Position-Based Animations
**`animation.pulse_position_animation(color, pos, pulse_size, slew_size=0, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates pulse at specific position
- **color**: ARGB color value or ValueProvider instance
- **pos**: Pixel position (0-based) or ValueProvider instance
- **pulse_size**: Width of pulse in pixels or ValueProvider instance
- **slew_size**: Fade region size in pixels or ValueProvider instance
- Returns: `PulsePositionAnimation` instance
```berry
var center_pulse = animation.pulse_position_animation(0xFFFFFFFF, 15, 3, 2)
var moving_pulse = animation.pulse_position_animation(0xFFFF0000, animation.smooth(0, 29, 3000), 3, 2)
```
**`animation.comet_animation(color, tail_length, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates moving comet effect
- **color**: ARGB color value or ValueProvider instance
- **tail_length**: Length of comet tail in pixels
- **speed_ms**: Movement speed in milliseconds per pixel
- Returns: `CometAnimation` instance
```berry
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
var rainbow_comet = animation.comet_animation(animation.rich_palette_color_provider(animation.PALETTE_RAINBOW, 3000), 8, 100)
```
**`animation.twinkle_animation(color, density, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates twinkling stars effect
- **color**: ARGB color value or ValueProvider instance
- **density**: Number of twinkling pixels
- **speed_ms**: Twinkle speed in milliseconds
- Returns: `TwinkleAnimation` instance
```berry
var stars = animation.twinkle_animation(0xFFFFFFFF, 5, 500)
var color_changing_stars = animation.twinkle_animation(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00, 0xFF0000FF], 4000), 5, 500)
```
### Fire and Natural Effects
**`animation.fire_animation(color=nil, intensity=200, speed_ms=100, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates realistic fire simulation
- **color**: ARGB color value, ValueProvider instance, or nil for default fire palette
- **intensity**: Fire intensity (0-255)
- **speed_ms**: Animation speed in milliseconds
- Returns: `FireAnimation` instance
```berry
var fire = animation.fire_animation(nil, 180, 150) # Default fire palette
var blue_fire = animation.fire_animation(0xFF0066FF, 180, 150) # Blue fire
```
### Advanced Pattern Animations
**`animation.noise_rainbow(scale, speed, strip_length, priority)`**
- Creates rainbow noise pattern with fractal complexity
- **scale**: Noise frequency/detail (0-255, higher = more detail)
- **speed**: Animation speed (0-255, 0 = static)
- **strip_length**: LED strip length
- **priority**: Rendering priority
- Returns: `NoiseAnimation` instance
**`animation.noise_single_color(color, scale, speed, strip_length, priority)`**
- Creates single-color noise pattern
- **color**: ARGB color value or ValueProvider instance
- Returns: `NoiseAnimation` instance
**`animation.noise_fractal(color, scale, speed, octaves, strip_length, priority)`**
- Creates multi-octave fractal noise
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
- **octaves**: Number of noise octaves (1-4)
- Returns: `NoiseAnimation` instance
```berry
var rainbow_noise = animation.noise_rainbow(60, 40, 30, 10)
var blue_noise = animation.noise_single_color(0xFF0066FF, 120, 60, 30, 10)
var fractal = animation.noise_fractal(nil, 40, 50, 3, 30, 10)
```
**`animation.plasma_rainbow(time_speed, strip_length, priority)`**
- Creates rainbow plasma effect using sine wave interference
- **time_speed**: Animation speed (0-255)
- Returns: `PlasmaAnimation` instance
**`animation.plasma_single_color(color, time_speed, strip_length, priority)`**
- Creates single-color plasma effect
- **color**: ARGB color value or ValueProvider instance
- Returns: `PlasmaAnimation` instance
```berry
var plasma = animation.plasma_rainbow(80, 30, 10)
var purple_plasma = animation.plasma_single_color(0xFF8800FF, 60, 30, 10)
```
**`animation.sparkle_white(density, fade_speed, strip_length, priority)`**
- Creates white twinkling sparkles
- **density**: Sparkle creation probability (0-255)
- **fade_speed**: Fade-out speed (0-255)
- Returns: `SparkleAnimation` instance
**`animation.sparkle_colored(color, density, fade_speed, strip_length, priority)`**
- Creates colored sparkles
- **color**: ARGB color value or ValueProvider instance
- Returns: `SparkleAnimation` instance
**`animation.sparkle_rainbow(density, fade_speed, strip_length, priority)`**
- Creates rainbow sparkles
- Returns: `SparkleAnimation` instance
```berry
var white_sparkles = animation.sparkle_white(80, 60, 30, 10)
var red_sparkles = animation.sparkle_colored(0xFFFF0000, 100, 50, 30, 10)
var rainbow_sparkles = animation.sparkle_rainbow(60, 40, 30, 10)
```
**`animation.wave_rainbow_sine(amplitude, wave_speed, strip_length, priority)`**
- Creates rainbow sine wave pattern
- **amplitude**: Wave amplitude/intensity (0-255)
- **wave_speed**: Wave movement speed (0-255)
- Returns: `WaveAnimation` instance
**`animation.wave_single_sine(color, amplitude, wave_speed, strip_length, priority)`**
- Creates single-color sine wave
- **color**: ARGB color value or ValueProvider instance
- Returns: `WaveAnimation` instance
**`animation.wave_custom(color, wave_type, amplitude, frequency, strip_length, priority)`**
- Creates custom wave with specified type
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
- **wave_type**: 0=sine, 1=triangle, 2=square, 3=sawtooth
- **frequency**: Wave frequency/density (0-255)
- Returns: `WaveAnimation` instance
```berry
var sine_wave = animation.wave_rainbow_sine(40, 80, 30, 10)
var green_wave = animation.wave_single_sine(0xFF00FF00, 60, 40, 30, 10)
var triangle_wave = animation.wave_custom(nil, 1, 50, 70, 30, 10)
```
### Motion Effect Animations
Motion effects transform existing animations by applying movement, scaling, and distortion effects.
**`animation.shift_scroll_right(source, speed, strip_length, priority)`**
- Scrolls animation to the right with wrapping
- **source**: Source animation to transform
- **speed**: Scroll speed (0-255)
- Returns: `ShiftAnimation` instance
**`animation.shift_scroll_left(source, speed, strip_length, priority)`**
- Scrolls animation to the left with wrapping
- Returns: `ShiftAnimation` instance
**`animation.shift_bounce_horizontal(source, speed, strip_length, priority)`**
- Bounces animation horizontally at strip edges
- Returns: `ShiftAnimation` instance
```berry
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
var scrolling = animation.shift_scroll_right(base, 100, 30, 10)
```
**`animation.bounce_gravity(source, speed, gravity, strip_length, priority)`**
- Physics-based bouncing with gravity simulation
- **source**: Source animation to transform
- **speed**: Initial bounce speed (0-255)
- **gravity**: Gravity strength (0-255)
- Returns: `BounceAnimation` instance
**`animation.bounce_basic(source, speed, damping, strip_length, priority)`**
- Basic bouncing without gravity
- **damping**: Damping factor (0-255, 255=no damping)
- Returns: `BounceAnimation` instance
```berry
var sparkles = animation.sparkle_white(80, 50, 30, 5)
var bouncing = animation.bounce_gravity(sparkles, 150, 80, 30, 10)
var elastic = animation.bounce_basic(sparkles, 120, 240, 30, 10)
```
**`animation.scale_static(source, scale_factor, strip_length, priority)`**
- Static scaling of animation
- **source**: Source animation to transform
- **scale_factor**: Scale factor (128=1.0x, 64=0.5x, 255=2.0x)
- Returns: `ScaleAnimation` instance
**`animation.scale_oscillate(source, speed, strip_length, priority)`**
- Oscillating scale (breathing effect)
- **speed**: Oscillation speed (0-255)
- Returns: `ScaleAnimation` instance
**`animation.scale_grow(source, speed, strip_length, priority)`**
- Growing scale effect
- Returns: `ScaleAnimation` instance
```berry
var pattern = animation.gradient_rainbow_linear(0, 30, 5)
var breathing = animation.scale_oscillate(pattern, 60, 30, 10)
var zoomed = animation.scale_static(pattern, 180, 30, 10) # 1.4x scale
```
**`animation.jitter_position(source, intensity, frequency, strip_length, priority)`**
- Random position shake effects
- **source**: Source animation to transform
- **intensity**: Jitter intensity (0-255)
- **frequency**: Jitter frequency (0-255, maps to 0-30 Hz)
- Returns: `JitterAnimation` instance
**`animation.jitter_color(source, intensity, frequency, strip_length, priority)`**
- Random color variations
- Returns: `JitterAnimation` instance
**`animation.jitter_brightness(source, intensity, frequency, strip_length, priority)`**
- Random brightness changes
- Returns: `JitterAnimation` instance
**`animation.jitter_all(source, intensity, frequency, strip_length, priority)`**
- Combination of position, color, and brightness jitter
- Returns: `JitterAnimation` instance
```berry
var base = animation.gradient_rainbow_linear(0, 30, 5)
var glitch = animation.jitter_all(base, 120, 100, 30, 15)
var shake = animation.jitter_position(base, 60, 40, 30, 10)
```
### Chaining Motion Effects
Motion effects can be chained together for complex transformations:
```berry
# Base animation
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
# Apply multiple transformations
var scaled = animation.scale_static(base, 150, 30, 8) # 1.2x scale
var shifted = animation.shift_scroll_left(scaled, 60, 30, 12) # Scroll left
var jittered = animation.jitter_color(shifted, 40, 30, 30, 15) # Add color jitter
# Result: A scaled, scrolling, color-jittered pulse
```
## Color System
### Color Formats
**ARGB Format**: `0xAARRGGBB`
- **AA**: Alpha channel (opacity) - usually `FF` for opaque
- **RR**: Red component (00-FF)
- **GG**: Green component (00-FF)
- **BB**: Blue component (00-FF)
```berry
var red = 0xFFFF0000 # Opaque red
var semi_blue = 0x800000FF # Semi-transparent blue
var white = 0xFFFFFFFF # Opaque white
var black = 0xFF000000 # Opaque black
```
### Predefined Colors
```berry
# Available as constants
animation.COLOR_RED # 0xFFFF0000
animation.COLOR_GREEN # 0xFF00FF00
animation.COLOR_BLUE # 0xFF0000FF
animation.COLOR_WHITE # 0xFFFFFFFF
animation.COLOR_BLACK # 0xFF000000
```
### Palette System
**Creating Palettes**
```berry
# VRGB format: Value(position), Red, Green, Blue
var fire_palette = bytes("00000000" "80FF0000" "FFFFFF00")
# ^pos=0 ^pos=128 ^pos=255
# black red yellow
```
**Predefined Palettes**
```berry
animation.PALETTE_RAINBOW # Standard rainbow colors
animation.PALETTE_FIRE # Fire effect colors
animation.PALETTE_OCEAN # Ocean wave colors
```
## Value Providers
Dynamic parameters that change over time.
### Static Values
```berry
# Regular values are automatically wrapped
var static_color = 0xFFFF0000
var static_position = 15
```
### Oscillator Providers
**`animation.smooth(start, end, period_ms)`**
- Smooth cosine wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.linear(start, end, period_ms)`**
- Triangle wave oscillation (goes from start to end, then back to start)
- Returns: `OscillatorValueProvider`
**`animation.triangle(start, end, period_ms)`**
- Alias for `linear()` - triangle wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.ramp(start, end, period_ms)`**
- Sawtooth wave oscillation (linear progression from start to end)
- Returns: `OscillatorValueProvider`
**`animation.sawtooth(start, end, period_ms)`**
- Alias for `ramp()` - sawtooth wave oscillation
- Returns: `OscillatorValueProvider`
**`animation.square(start, end, period_ms, duty_cycle=50)`**
- Square wave oscillation
- **duty_cycle**: Percentage of time at high value
- Returns: `OscillatorValueProvider`
```berry
# Dynamic position that moves back and forth
var moving_pos = animation.smooth(0, 29, 3000)
# Dynamic color that cycles brightness
var breathing_color = animation.smooth(50, 255, 2000)
# Use with animations
var dynamic_pulse = animation.pulse_position_animation(
0xFFFF0000, # Static red color
moving_pos, # Dynamic position
3, # Static pulse size
1 # Static slew size
)
```
## Event System
### Event Registration
**`animation.register_event_handler(event_name, callback, priority=0, condition=nil, metadata=nil)`**
- Registers an event handler
- **event_name**: Name of event to handle
- **callback**: Function to call when event occurs
- **priority**: Handler priority (higher = executed first)
- **condition**: Optional condition function
- **metadata**: Optional metadata map
- Returns: `EventHandler` instance
```berry
def flash_white(event_data)
var flash = animation.solid(0xFFFFFFFF)
engine.add_animation(flash)
end
var handler = animation.register_event_handler("button_press", flash_white, 10)
```
### Event Triggering
**`animation.trigger_event(event_name, event_data={})`**
- Triggers an event
- **event_name**: Name of event to trigger
- **event_data**: Data to pass to handlers
```berry
animation.trigger_event("button_press", {"button": "main"})
```
## DSL System
### DSL Runtime
**`animation.DSLRuntime(engine, debug_mode=false)`**
- Creates DSL runtime instance
- **engine**: AnimationEngine instance
- **debug_mode**: Enable debug output
- Returns: `DSLRuntime` instance
#### Methods
**`load_dsl(source_code)`**
- Compiles and executes DSL source code
- **source_code**: DSL source as string
- Returns: `true` on success, `false` on error
**`load_dsl_file(filename)`**
- Loads and executes DSL from file
- **filename**: Path to .anim file
- Returns: `true` on success, `false` on error
```berry
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
var dsl_code = '''
color red = #FF0000
animation pulse_red = pulse(solid(red), 2s, 50%, 100%)
run pulse_red
'''
if runtime.load_dsl(dsl_code)
print("Animation loaded successfully")
else
print("Failed to load animation")
end
```
### DSL Compilation
**`animation.compile_dsl(source_code)`**
- Compiles DSL to Berry code
- **source_code**: DSL source as string
- Returns: Berry code string or raises exception
- Raises: `"dsl_compilation_error"` on compilation failure
```berry
try
var berry_code = animation.compile_dsl(dsl_source)
print("Generated code:", berry_code)
var compiled_func = compile(berry_code)
compiled_func()
except "dsl_compilation_error" as e, msg
print("Compilation error:", msg)
end
```
## User Functions
### Function Registration
**`animation.register_user_function(name, func)`**
- Registers Berry function for DSL use
- **name**: Function name for DSL
- **func**: Berry function to register
**`animation.is_user_function(name)`**
- Checks if function is registered
- Returns: `true` if registered
**`animation.get_user_function(name)`**
- Gets registered function
- Returns: Function or `nil`
**`animation.list_user_functions()`**
- Lists all registered function names
- Returns: Array of function names
```berry
def custom_breathing(color, period)
return animation.pulse(animation.solid(color), period, 50, 255)
end
animation.register_user_function("breathing", custom_breathing)
# Now available in DSL:
# animation my_effect = breathing(red, 3s)
```
## Version Information
The framework uses a numeric version system for efficient comparison:
```berry
# Primary version (0xAABBCCDD format: AA=major, BB=minor, CC=patch, DD=build)
print(f"0x{animation.VERSION:08X}") # 0x00010000
# Convert to string format (drops build number)
print(animation.version_string()) # "0.1.0"
# Convert any version number to string
print(animation.version_string(0x01020304)) # "1.2.3"
# Extract components manually
var major = (animation.VERSION >> 24) & 0xFF # 0
var minor = (animation.VERSION >> 16) & 0xFF # 1
var patch = (animation.VERSION >> 8) & 0xFF # 0
var build = animation.VERSION & 0xFF # 0
# Version comparison
var is_new_enough = animation.VERSION >= 0x00010000 # v0.1.0+
```
## Utility Functions
### Global Variable Access
**`animation.global(name)`**
- Safely accesses global variables
- **name**: Variable name
- Returns: Variable value
- Raises: `"syntax_error"` if variable doesn't exist
```berry
# Set global variable
global.my_color = 0xFFFF0000
# Access safely
var color = animation.global("my_color") # Returns 0xFFFF0000
var missing = animation.global("missing") # Raises exception
```
### Type Checking
**`animation.is_value_provider(obj)`**
- Checks if object is a ValueProvider
- Returns: `true` if object implements ValueProvider interface
**`animation.is_color_provider(obj)`**
- Checks if object is a ColorProvider
- Returns: `true` if object implements ColorProvider interface
```berry
var static_val = 42
var dynamic_val = animation.smooth(0, 100, 2000)
print(animation.is_value_provider(static_val)) # false
print(animation.is_value_provider(dynamic_val)) # true
```
## Error Handling
### Common Exceptions
- **`"dsl_compilation_error"`** - DSL compilation failed
- **`"syntax_error"`** - Variable not found or syntax error
- **`"type_error"`** - Invalid parameter type
- **`"runtime_error"`** - General runtime error
### Best Practices
```berry
# Always use try/catch for DSL operations
try
runtime.load_dsl(dsl_code)
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
except .. as e, msg
print("Unexpected error:", msg)
end
# Check engine state before operations
if engine.is_active()
engine.add_animation(new_animation)
else
print("Engine not running")
end
# Validate parameters
if type(color) == "int" && color >= 0
var anim = animation.solid(color)
else
print("Invalid color value")
end
```
## Performance Tips
### Memory Management
```berry
# Clear animations when switching effects
engine.clear()
engine.add_animation(new_animation)
# Reuse animation objects when possible
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
# Use pulse_red multiple times instead of creating new instances
```
### Timing Optimization
```berry
# Use longer periods for smoother performance
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
var choppy_pulse = animation.pulse(pattern, 100, 50, 255) # 100ms - may be choppy
# Limit simultaneous animations
# Good: 1-3 animations
# Avoid: 10+ animations running simultaneously
```
### Value Provider Efficiency
```berry
# Efficient: Reuse providers
var breathing = animation.smooth(50, 255, 2000)
var anim1 = animation.pulse(pattern1, breathing)
var anim2 = animation.pulse(pattern2, breathing) # Reuse same provider
# Inefficient: Create new providers
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000)) # Duplicate
```
This API reference covers the essential classes and functions. For more advanced usage, see the [Examples](EXAMPLES.md) and [User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md) documentation.

Some files were not shown because too many files have changed in this diff Show More