Preview of Berry animation framework (#23740)

* Preview of Berry animation framework

* fix comet and compilation for safeboot
This commit is contained in:
s-hadinger 2025-08-01 18:02:02 +02:00 committed by GitHub
parent 4d2162507a
commit ca934bae33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
243 changed files with 79304 additions and 677 deletions

View File

@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Basic support for ESP32-P4 (#23663)
- ESP32-P4 command `HostedOta` (#23675)
- Support for RV3028 RTC (#23672)
- Preview of Berry animation framework
### Breaking Changed

View File

@ -83,6 +83,11 @@ be_extern_native_module(haspmota);
#endif // USE_LVGL_HASPMOTA
#endif // USE_LVGL
#ifdef USE_MATTER_DEVICE
#ifdef USE_WS2812
#ifdef USE_BERRY_ANIMATION
be_extern_native_module(animation);
#endif // USE_BERRY_ANIMATION
#endif // USE_WS2812
be_extern_native_module(matter);
#endif // USE_MATTER_DEVICE
@ -217,6 +222,11 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = {
#ifdef USE_MATTER_DEVICE
&be_native_module(matter),
#endif // USE_MATTER_DEVICE
#ifdef USE_WS2812
#ifdef USE_BERRY_ANIMATION
&be_native_module(animation),
#endif // USE_BERRY_ANIMATION
#endif // USE_WS2812
#endif // TASMOTA
CUSTOM_NATIVE_MODULES
/* user-defined modules register end */

View File

@ -107,6 +107,7 @@ struct arg_opts {
const char *src;
const char *dst;
const char *modulepath;
const char *execute;
};
/* check if the character is a letter */
@ -214,9 +215,7 @@ static int handle_result(bvm *vm, int res)
/* execute a script source or file and output a result or error */
static int doscript(bvm *vm, const char *name, int args)
{
/* load string, bytecode file or compile script file */
int res = args & arg_e ? /* check script source string */
be_loadstring(vm, name) : be_loadmode(vm, name, args & arg_l);
int res = be_loadmode(vm, name, args & arg_l);
if (res == BE_OK) { /* parsing succeeded */
res = be_pcall(vm, 0); /* execute */
}
@ -226,17 +225,25 @@ static int doscript(bvm *vm, const char *name, int args)
/* load a Berry script string or file and execute
* args: the enabled options mask
* */
static int load_script(bvm *vm, int argc, char *argv[], int args)
static int load_script(bvm *vm, int argc, char *argv[], int args, const char * script)
{
int res = 0;
int repl_mode = args & arg_i || (args == 0 && argc == 0);
if (repl_mode) { /* enter the REPL mode after executing the script file */
be_writestring(repl_prelude);
}
if (argc > 0) { /* check file path or source string argument */
/* compile script file */
if (script) {
res = be_loadstring(vm, script);
if (res == BE_OK) { /* parsing succeeded */
res = be_pcall(vm, 0); /* execute */
}
res = handle_result(vm, res);
}
if (res == BE_OK && argc > 0) { /* check file path or source string argument */
res = doscript(vm, argv[0], args);
}
if (repl_mode) { /* enter the REPL mode */
if (res == BE_OK && repl_mode) { /* enter the REPL mode */
res = be_repl(vm, get_line, free_line);
if (res == -BE_MALLOC_FAIL) {
be_writestring("error: memory allocation failed.\n");
@ -266,9 +273,12 @@ static int parse_arg(struct arg_opts *opt, int argc, char *argv[])
case 'v': args |= arg_v; break;
case 'i': args |= arg_i; break;
case 'l': args |= arg_l; break;
case 'e': args |= arg_e; break;
case 'g': args |= arg_g; break;
case 's': args |= arg_s; break;
case 'e':
args |= arg_e;
opt->execute = opt->optarg;
break;
case 'm':
args |= arg_m;
opt->modulepath = opt->optarg;
@ -356,7 +366,7 @@ static int analysis_args(bvm *vm, int argc, char *argv[])
{
int args = 0;
struct arg_opts opt = { 0 };
opt.pattern = "m?vhilegsc?o?";
opt.pattern = "m?vhile?gsc?o?";
args = parse_arg(&opt, argc, argv);
argc -= opt.idx;
argv += opt.idx;
@ -397,7 +407,7 @@ static int analysis_args(bvm *vm, int argc, char *argv[])
}
return build_file(vm, opt.dst, opt.src, args);
}
return load_script(vm, argc, argv, args);
return load_script(vm, argc, argv, args, opt.execute);
}

View File

@ -5,4 +5,4 @@
# Included in the Platformio build process with `pio-tools/gen-berry-structures.py
#
rm -Rf ./generate/be_*.h
python3 tools/coc/coc -o generate src default ../berry_tasmota/src ../berry_mapping/src ../berry_int64/src ../../libesp32_lvgl/lv_binding_berry/src ../../libesp32_lvgl/lv_haspmota/src/solidify ../berry_matter/src/solidify ../berry_matter/src ../berry_animate/src/solidify ../berry_animate/src ../../libesp32_lvgl/lv_binding_berry/src/solidify ../../libesp32_lvgl/lv_binding_berry/generate -c default/berry_conf.h
python3 tools/coc/coc -o generate src default ../berry_tasmota/src ../berry_mapping/src ../berry_int64/src ../../libesp32_lvgl/lv_binding_berry/src ../../libesp32_lvgl/lv_haspmota/src/solidify ../berry_matter/src/solidify ../berry_matter/src ../berry_animate/src/solidify ../berry_animate/src ../berry_animation/src/solidify ../berry_animation/src ../../libesp32_lvgl/lv_binding_berry/src/solidify ../../libesp32_lvgl/lv_binding_berry/generate -c default/berry_conf.h

View File

@ -0,0 +1,215 @@
# 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

@ -0,0 +1,36 @@
# Aurora Borealis - Northern lights simulation
# Flowing green and purple aurora colors
#strip length 60
# Define aurora color palette
palette aurora_colors = [
(0, #000022), # Dark night sky
(64, #004400), # Dark green
(128, #00AA44), # Aurora green
(192, #44AA88), # Light green
(255, #88FFAA) # Bright aurora
]
# Secondary purple palette
palette aurora_purple = [
(0, #220022), # Dark purple
(64, #440044), # Medium purple
(128, #8800AA), # Bright purple
(192, #AA44CC), # Light purple
(255, #CCAAFF) # Pale 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)
)
sequence demo {
play aurora_base # infinite duration (no 'for' clause)
}
run demo

View File

@ -0,0 +1,41 @@
# Breathing Colors - Slow color breathing effect
# Gentle pulsing through different colors
strip length 60
# Define breathing colors
color breathe_red = #FF0000
color breathe_green = #00FF00
color breathe_blue = #0000FF
color breathe_purple = #800080
color breathe_orange = #FF8000
# Create breathing animation that cycles through colors
palette breathe_palette = [
(0, #FF0000), # Red
(51, #FF8000), # Orange
(102, #FFFF00), # Yellow
(153, #00FF00), # Green
(204, #0000FF), # Blue
(255, #800080) # Purple
]
# Create a rich palette color provider
pattern palette_pattern = rich_palette_animation(
breathe_palette, # palette
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)
)
# Add gentle opacity breathing
breathing.opacity = smooth(100, 255, 4s)
# Start the animation
run breathing

View File

@ -0,0 +1,46 @@
# Candy Cane - Red and white stripes
# Classic Christmas candy cane pattern
strip length 60
# Define stripe colors
color candy_red = #FF0000
color candy_white = #FFFFFF
# Create alternating red and white pattern
# 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)
# 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)
# Start all stripes
run stripe1
run stripe2
run stripe3
run stripe4
run stripe5
run stripe6
run stripe7
run stripe8
run stripe9
run stripe10

View File

@ -0,0 +1,60 @@
# Christmas Tree - Festive holiday colors
# Green base with colorful ornaments and twinkling
strip length 60
# Green tree base
color tree_green = #006600
animation tree_base = solid(tree_green)
# Define ornament colors
palette ornament_colors = [
(0, #FF0000), # Red
(64, #FFD700), # Gold
(128, #0000FF), # Blue
(192, #FFFFFF), # White
(255, #FF00FF) # Magenta
]
# Colorful ornaments as twinkling lights
pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
animation ornaments = twinkle_animation(
ornament_pattern, # color source
15, # density (many ornaments)
800ms # twinkle speed (slow twinkle)
)
ornaments.priority = 10
# Star on top (bright yellow pulse)
animation tree_star = pulse_position_animation(
#FFFF00, # Bright yellow
58, # position (near the top)
3, # star size
1 # sharp edges
)
tree_star.priority = 20
tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
# Add some white sparkles for snow/magic
animation snow_sparkles = twinkle_animation(
#FFFFFF, # White snow
8, # density (sparkle count)
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)
animation garland = comet_animation(
garland_pattern, # color source
6, # garland length (tail length)
4s # slow movement (speed)
)
garland.priority = 5
# Start all animations
run tree_base
run ornaments
run tree_star
run snow_sparkles
run garland

View File

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

View File

@ -0,0 +1,86 @@
# DSL Compilation Report
Generated: Ven 1 aoû 2025 16:04:44 CEST
## Summary
- **Total files**: 25
- **Successfully compiled**: 25
- **Failed to compile**: 0
- **Success rate**: 100%
## Successfully Compiled Files
- ✅ aurora_borealis.anim
- ✅ breathing_colors.anim
- ✅ candy_cane.anim
- ✅ christmas_tree.anim
- ✅ comet_chase.anim
- ✅ disco_strobe.anim
- ✅ fire_flicker.anim
- ✅ heartbeat_pulse.anim
- ✅ lava_lamp.anim
- ✅ lightning_storm.anim
- ✅ matrix_rain.anim
- ✅ meteor_shower.anim
- ✅ neon_glow.anim
- ✅ ocean_waves.anim
- ✅ palette_demo.anim
- ✅ palette_showcase.anim
- ✅ pattern_animation_demo.anim
- ✅ plasma_wave.anim
- ✅ police_lights.anim
- ✅ property_assignment_demo.anim
- ✅ rainbow_cycle.anim
- ✅ scanner_larson.anim
- ✅ simple_palette.anim
- ✅ sunrise_sunset.anim
- ✅ twinkle_stars.anim
## Failed Compilations
## Common Issues Found
Based on the compilation attempts, the following issues are common:
### 1. Comments in Palette Definitions
Many files fail because comments are included within palette array definitions:
```
palette fire_colors = [
(0, #000000), # This comment causes parsing errors
(128, #FF0000) # This too
]
```
**Solution**: Remove comments from within palette definitions.
### 2. Comments in Function Arguments
Comments within function calls break the parser:
```
animation pulse_red = pulse(
solid(red),
2s, # This comment breaks parsing
20%, 100%
)
```
**Solution**: Remove comments from function argument lists.
### 3. Missing Function Parameters
Some function calls expect specific parameter formats that aren't provided.
### 4. Property Assignments Not Supported
Object property assignments like `stripe1.pos = 3` are not handled correctly.
## Recommendations
1. **Clean DSL Syntax**: Remove all inline comments from complex expressions
2. **Full Parameter Lists**: Always provide complete parameter lists to functions
3. **Use Sequences**: Instead of property assignments, use sequence-based approaches
4. **Test Incrementally**: Start with simple examples and build complexity gradually
## Working Examples
The successfully compiled files can be used as templates for creating new DSL animations.

View File

@ -0,0 +1,126 @@
# 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

@ -0,0 +1,75 @@
# Generated Berry code from Animation DSL
# Source: aurora_borealis.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:
# # Aurora Borealis - Northern lights simulation
# # Flowing green and purple aurora colors
#
# #strip length 60
#
# # Define aurora color palette
# palette aurora_colors = [
# (0, #000022), # Dark night sky
# (64, #004400), # Dark green
# (128, #00AA44), # Aurora green
# (192, #44AA88), # Light green
# (255, #88FFAA) # Bright aurora
# ]
#
# # Secondary purple palette
# palette aurora_purple = [
# (0, #220022), # Dark purple
# (64, #440044), # Medium purple
# (128, #8800AA), # Bright purple
# (192, #AA44CC), # Light purple
# (255, #CCAAFF) # Pale 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)
# )
#
# sequence demo {
# play aurora_base # infinite duration (no 'for' clause)
# }
#
# run demo
import animation
# Aurora Borealis - Northern lights simulation
# Flowing green and purple aurora colors
#strip length 60
# Define aurora color palette
# Auto-generated strip initialization (using Tasmota configuration)
var strip = global.Leds() # Get strip length from Tasmota configuration
var engine = animation.create_engine(strip)
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)
def sequence_demo()
var steps = []
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
engine.start()

View File

@ -0,0 +1,79 @@
# Generated Berry code from Animation DSL
# Source: breathing_colors.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:
# # Breathing Colors - Slow color breathing effect
# # Gentle pulsing through different colors
#
# strip length 60
#
# # Define breathing colors
# color breathe_red = #FF0000
# color breathe_green = #00FF00
# color breathe_blue = #0000FF
# color breathe_purple = #800080
# color breathe_orange = #FF8000
#
# # Create breathing animation that cycles through colors
# palette breathe_palette = [
# (0, #FF0000), # Red
# (51, #FF8000), # Orange
# (102, #FFFF00), # Yellow
# (153, #00FF00), # Green
# (204, #0000FF), # Blue
# (255, #800080) # Purple
# ]
#
# # Create a rich palette color provider
# pattern palette_pattern = rich_palette_animation(
# breathe_palette, # palette
# 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)
# )
#
# # Add gentle opacity breathing
# breathing.opacity = smooth(100, 255, 4s)
#
# # Start the animation
# run breathing
import animation
# Breathing Colors - Slow color breathing effect
# Gentle pulsing through different colors
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define breathing colors
var breathe_red_ = 0xFFFF0000
var breathe_green_ = 0xFF00FF00
var breathe_blue_ = 0xFF0000FF
var breathe_purple_ = 0xFF800080
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)
# Create breathing animation using the palette
var breathing_ = animation.breathe(animation.global('palette_pattern_', 'palette_pattern'), 100, 255, 4000)
# Add gentle opacity breathing
animation.global('breathing_').opacity = animation.smooth(100, 255, 4000)
# Start the animation
# Start all animations/sequences
if global.contains('sequence_breathing')
var seq_manager = global.sequence_breathing()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('breathing_'))
end
engine.start()

View File

@ -0,0 +1,151 @@
# Generated Berry code from Animation DSL
# Source: candy_cane.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:
# # Candy Cane - Red and white stripes
# # Classic Christmas candy cane pattern
#
# strip length 60
#
# # Define stripe colors
# color candy_red = #FF0000
# color candy_white = #FFFFFF
#
# # Create alternating red and white pattern
# # 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)
#
# # 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)
#
# # Start all stripes
# run stripe1
# run stripe2
# run stripe3
# run stripe4
# run stripe5
# run stripe6
# run stripe7
# run stripe8
# run stripe9
# run stripe10
import animation
# Candy Cane - Red and white stripes
# Classic Christmas candy cane pattern
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define stripe colors
var candy_red_ = 0xFFFF0000
var candy_white_ = 0xFFFFFFFF
# Create alternating red and white pattern
# 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)
# 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'))
# Start all stripes
# Start all animations/sequences
if global.contains('sequence_stripe1')
var seq_manager = global.sequence_stripe1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe1_'))
end
if global.contains('sequence_stripe2')
var seq_manager = global.sequence_stripe2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe2_'))
end
if global.contains('sequence_stripe3')
var seq_manager = global.sequence_stripe3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe3_'))
end
if global.contains('sequence_stripe4')
var seq_manager = global.sequence_stripe4()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe4_'))
end
if global.contains('sequence_stripe5')
var seq_manager = global.sequence_stripe5()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe5_'))
end
if global.contains('sequence_stripe6')
var seq_manager = global.sequence_stripe6()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe6_'))
end
if global.contains('sequence_stripe7')
var seq_manager = global.sequence_stripe7()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe7_'))
end
if global.contains('sequence_stripe8')
var seq_manager = global.sequence_stripe8()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe8_'))
end
if global.contains('sequence_stripe9')
var seq_manager = global.sequence_stripe9()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe9_'))
end
if global.contains('sequence_stripe10')
var seq_manager = global.sequence_stripe10()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stripe10_'))
end
engine.start()

View File

@ -0,0 +1,128 @@
# Generated Berry code from Animation DSL
# Source: christmas_tree.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:
# # Christmas Tree - Festive holiday colors
# # Green base with colorful ornaments and twinkling
#
# strip length 60
#
# # Green tree base
# color tree_green = #006600
# animation tree_base = solid(tree_green)
#
# # Define ornament colors
# palette ornament_colors = [
# (0, #FF0000), # Red
# (64, #FFD700), # Gold
# (128, #0000FF), # Blue
# (192, #FFFFFF), # White
# (255, #FF00FF) # Magenta
# ]
#
# # Colorful ornaments as twinkling lights
# pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
# animation ornaments = twinkle_animation(
# ornament_pattern, # color source
# 15, # density (many ornaments)
# 800ms # twinkle speed (slow twinkle)
# )
# ornaments.priority = 10
#
# # Star on top (bright yellow pulse)
# animation tree_star = pulse_position_animation(
# #FFFF00, # Bright yellow
# 58, # position (near the top)
# 3, # star size
# 1 # sharp edges
# )
# tree_star.priority = 20
# tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
#
# # Add some white sparkles for snow/magic
# animation snow_sparkles = twinkle_animation(
# #FFFFFF, # White snow
# 8, # density (sparkle count)
# 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)
# animation garland = comet_animation(
# garland_pattern, # color source
# 6, # garland length (tail length)
# 4s # slow movement (speed)
# )
# garland.priority = 5
#
# # Start all animations
# run tree_base
# run ornaments
# run tree_star
# run snow_sparkles
# run garland
import animation
# Christmas Tree - Festive holiday colors
# Green base with colorful ornaments and twinkling
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Green tree base
var tree_green_ = 0xFF006600
var tree_base_ = animation.solid(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)
animation.global('ornaments_').priority = 10
# Star on top (bright yellow pulse)
var tree_star_ = animation.pulse_position_animation(0xFFFFFF00, 58, 3, 1)
animation.global('tree_star_').priority = 20
animation.global('tree_star_').opacity = animation.smooth(200, 255, 2000) # Gentle pulsing
# Add some white sparkles for snow/magic
var snow_sparkles_ = animation.twinkle_animation(0xFFFFFFFF, 8, 400)
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)
animation.global('garland_').priority = 5
# Start all animations
# Start all animations/sequences
if global.contains('sequence_tree_base')
var seq_manager = global.sequence_tree_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('tree_base_'))
end
if global.contains('sequence_ornaments')
var seq_manager = global.sequence_ornaments()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('ornaments_'))
end
if global.contains('sequence_tree_star')
var seq_manager = global.sequence_tree_star()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('tree_star_'))
end
if global.contains('sequence_snow_sparkles')
var seq_manager = global.sequence_snow_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('snow_sparkles_'))
end
if global.contains('sequence_garland')
var seq_manager = global.sequence_garland()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('garland_'))
end
engine.start()

View File

@ -0,0 +1,93 @@
# Generated Berry code from Animation DSL
# Source: comet_chase.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:
# # Comet Chase - Moving comet with trailing tail
# # Bright head with fading tail
#
# strip length 60
#
# # Dark blue background
# color space_blue = #000066 # Note: opaque 0xFF alpha channel is implicitly added
# animation background = solid(space_blue)
#
# # Main comet with bright white head
# animation comet_main = comet_animation(
# #FFFFFF, # White head
# 10, # tail length
# 2s # speed
# )
# comet_main.priority = 7
#
# # Secondary comet in different color, opposite direction
# animation comet_secondary = comet_animation(
# #FF4500, # Orange head
# 8, # shorter tail
# 3s, # slower speed
# -1 # other direction
# )
# comet_secondary.priority = 5
#
# # Add sparkle trail behind comets but on top of blue background
# animation comet_sparkles = twinkle_animation(
# #AAAAFF, # Light blue sparkles
# 8, # density (moderate sparkles)
# 400ms # twinkle speed (quick sparkle)
# )
# comet_sparkles.priority = 8
#
# # Start all animations
# run background
# run comet_main
# run comet_secondary
# run comet_sparkles
import animation
# Comet Chase - Moving comet with trailing tail
# Bright head with fading tail
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark blue background
var space_blue_ = 0xFF000066 # Note: opaque 0xFF alpha channel is implicitly added
var background_ = animation.solid(animation.global('space_blue_', 'space_blue'))
# Main comet with bright white head
var comet_main_ = animation.comet_animation(0xFFFFFFFF, 10, 2000)
animation.global('comet_main_').priority = 7
# Secondary comet in different color, opposite direction
var comet_secondary_ = animation.comet_animation(0xFFFF4500, 8, 3000, -1)
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)
animation.global('comet_sparkles_').priority = 8
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_comet_main')
var seq_manager = global.sequence_comet_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_main_'))
end
if global.contains('sequence_comet_secondary')
var seq_manager = global.sequence_comet_secondary()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_secondary_'))
end
if global.contains('sequence_comet_sparkles')
var seq_manager = global.sequence_comet_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('comet_sparkles_'))
end
engine.start()

View File

@ -0,0 +1,113 @@
# Generated Berry code from Animation DSL
# Source: disco_strobe.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:
# # Disco Strobe - Fast colorful strobing
# # Rapid color changes with strobe effects
#
# strip length 60
#
# # Define disco color palette
# palette disco_colors = [
# (0, #FF0000), # Red
# (42, #FF8000), # Orange
# (85, #FFFF00), # Yellow
# (128, #00FF00), # Green
# (170, #0000FF), # Blue
# (213, #8000FF), # Purple
# (255, #FF00FF) # Magenta
# ]
#
# # Fast color cycling base
# animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
#
# # Add strobe effect
# disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
#
# # Add white flash overlay
# animation white_flash = solid(#FFFFFF)
# white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
# white_flash.priority = 20
#
# # Add colored sparkles
# pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
# animation disco_sparkles = twinkle_animation(
# sparkle_pattern, # color source
# 12, # density (many sparkles)
# 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)
# )
# disco_pulse.priority = 10
# disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
#
# # Start all animations
# run disco_base
# run white_flash
# run disco_sparkles
# run disco_pulse
import animation
# Disco Strobe - Fast colorful strobing
# Rapid color changes with strobe effects
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define disco color palette
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)
# Add strobe effect
animation.global('disco_base_').opacity = animation.square(0, 255, 100, 30) # 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
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)
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)
animation.global('disco_pulse_').priority = 10
animation.global('disco_pulse_').pos = animation.sawtooth(4, 56, 2000) # Fast movement
# Start all animations
# Start all animations/sequences
if global.contains('sequence_disco_base')
var seq_manager = global.sequence_disco_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_base_'))
end
if global.contains('sequence_white_flash')
var seq_manager = global.sequence_white_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('white_flash_'))
end
if global.contains('sequence_disco_sparkles')
var seq_manager = global.sequence_disco_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_sparkles_'))
end
if global.contains('sequence_disco_pulse')
var seq_manager = global.sequence_disco_pulse()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('disco_pulse_'))
end
engine.start()

View File

@ -0,0 +1,72 @@
# Generated Berry code from Animation DSL
# Source: fire_flicker.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:
# # Fire Flicker - Realistic fire simulation
# # Warm colors with random flickering intensity
#
# strip length 60
#
# # Define fire palette from black to yellow
# palette fire_colors = [
# (0, #000000), # Black
# (64, #800000), # Dark red
# (128, #FF0000), # Red
# (192, #FF4500), # Orange red
# (255, #FFFF00) # Yellow
# ]
#
# # Create base fire animation with palette
# animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
#
# # Add flickering effect with random intensity changes
# fire_base.opacity = smooth(180, 255, 800ms)
#
# # Add subtle position variation for more realism
# pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
# animation fire_flicker = twinkle_animation(
# flicker_pattern, # color source
# 12, # density (number of flickers)
# 200ms # twinkle speed (flicker duration)
# )
# fire_flicker.priority = 10
#
# # Start both animations
# run fire_base
# run fire_flicker
import animation
# Fire Flicker - Realistic fire simulation
# Warm colors with random flickering intensity
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define fire palette from black to yellow
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)
# Add flickering effect with random intensity changes
animation.global('fire_base_').opacity = animation.smooth(180, 255, 800)
# 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)
animation.global('fire_flicker_').priority = 10
# Start both animations
# Start all animations/sequences
if global.contains('sequence_fire_base')
var seq_manager = global.sequence_fire_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('fire_base_'))
end
if global.contains('sequence_fire_flicker')
var seq_manager = global.sequence_fire_flicker()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('fire_flicker_'))
end
engine.start()

View File

@ -0,0 +1,111 @@
# Generated Berry code from Animation DSL
# Source: heartbeat_pulse.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:
# # Heartbeat Pulse - Rhythmic double pulse
# # Red pulsing like a heartbeat
#
# strip length 60
#
# # Dark background
# color heart_bg = #110000
# animation background = solid(heart_bg)
#
# # Define heartbeat timing - double pulse pattern
# # First pulse (stronger)
# animation heartbeat1 = solid(#FF0000) # Bright red
# heartbeat1.opacity = square(0, 255, 150ms, 20) # Quick strong pulse
# heartbeat1.priority = 10
#
# # Second pulse (weaker, slightly delayed)
# animation heartbeat2 = solid(#CC0000) # Slightly dimmer red
# # Delay the second pulse by adjusting the square wave phase
# heartbeat2.opacity = square(0, 180, 150ms, 15) # Weaker pulse
# heartbeat2.priority = 8
#
# # Add subtle glow effect
# animation heart_glow = solid(#660000) # Dim red glow
# heart_glow.opacity = smooth(30, 100, 1s) # Gentle breathing glow
# heart_glow.priority = 5
#
# # Add center pulse for emphasis
# animation center_pulse = pulse_position_animation(
# #FFFFFF, # White center
# 30, # center of strip
# 4, # small center
# 2 # soft edges
# )
# center_pulse.priority = 20
# center_pulse.opacity = square(0, 200, 100ms, 10) # Quick white flash
#
# # Start all animations
# run background
# run heart_glow
# run heartbeat1
# run heartbeat2
# run center_pulse
import animation
# Heartbeat Pulse - Rhythmic double pulse
# Red pulsing like a heartbeat
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark background
var heart_bg_ = 0xFF110000
var background_ = animation.solid(animation.global('heart_bg_', 'heart_bg'))
# Define heartbeat timing - double pulse pattern
# First pulse (stronger)
var heartbeat1_ = animation.solid(0xFFFF0000) # Bright red
animation.global('heartbeat1_').opacity = animation.square(0, 255, 150, 20) # Quick strong pulse
animation.global('heartbeat1_').priority = 10
# Second pulse (weaker, slightly delayed)
var heartbeat2_ = animation.solid(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
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
animation.global('heart_glow_').priority = 5
# Add center pulse for emphasis
var center_pulse_ = animation.pulse_position_animation(0xFFFFFFFF, 30, 4, 2)
animation.global('center_pulse_').priority = 20
animation.global('center_pulse_').opacity = animation.square(0, 200, 100, 10) # Quick white flash
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_heart_glow')
var seq_manager = global.sequence_heart_glow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heart_glow_'))
end
if global.contains('sequence_heartbeat1')
var seq_manager = global.sequence_heartbeat1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heartbeat1_'))
end
if global.contains('sequence_heartbeat2')
var seq_manager = global.sequence_heartbeat2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heartbeat2_'))
end
if global.contains('sequence_center_pulse')
var seq_manager = global.sequence_center_pulse()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('center_pulse_'))
end
engine.start()

View File

@ -0,0 +1,132 @@
# Generated Berry code from Animation DSL
# Source: lava_lamp.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:
# # Lava Lamp - Slow flowing warm colors
# # Organic movement like a lava lamp
#
# strip length 60
#
# # Define lava colors (warm oranges and reds)
# palette lava_colors = [
# (0, #330000), # Dark red
# (64, #660000), # Medium red
# (128, #CC3300), # Bright red
# (192, #FF6600), # Orange
# (255, #FFAA00) # Yellow-orange
# ]
#
# # Base lava animation - very slow color changes
# animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 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
# )
# lava_blob1.priority = 10
# lava_blob1.pos = smooth(9, 51, 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
# )
# lava_blob2.priority = 8
# lava_blob2.pos = smooth(46, 14, 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
# )
# lava_blob3.priority = 6
# lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
#
# # Add subtle heat shimmer effect
# pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
# animation heat_shimmer = twinkle_animation(
# shimmer_pattern, # color source
# 6, # density (shimmer points)
# 1.5s # twinkle speed (slow shimmer)
# )
# heat_shimmer.priority = 15
#
# # Start all animations
# run lava_base
# run lava_blob1
# run lava_blob2
# run lava_blob3
# run heat_shimmer
import animation
# Lava Lamp - Slow flowing warm colors
# Organic movement like a lava lamp
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define lava colors (warm oranges and reds)
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)
# 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)
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)
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)
animation.global('lava_blob3_').priority = 6
animation.global('lava_blob3_').pos = animation.smooth(25, 35, 18000) # 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)
animation.global('heat_shimmer_').priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_lava_base')
var seq_manager = global.sequence_lava_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_base_'))
end
if global.contains('sequence_lava_blob1')
var seq_manager = global.sequence_lava_blob1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob1_'))
end
if global.contains('sequence_lava_blob2')
var seq_manager = global.sequence_lava_blob2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob2_'))
end
if global.contains('sequence_lava_blob3')
var seq_manager = global.sequence_lava_blob3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lava_blob3_'))
end
if global.contains('sequence_heat_shimmer')
var seq_manager = global.sequence_heat_shimmer()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('heat_shimmer_'))
end
engine.start()

View File

@ -0,0 +1,114 @@
# Generated Berry code from Animation DSL
# Source: lightning_storm.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:
# # Lightning Storm - Random lightning flashes
# # Dark stormy background with bright lightning
#
# strip length 60
#
# # Dark stormy background with subtle purple/blue
# palette storm_colors = [
# (0, #000011), # Very dark blue
# (128, #110022), # Dark purple
# (255, #220033) # Slightly lighter purple
# ]
#
# animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
#
# # Random lightning flashes - full strip
# animation lightning_main = solid(#FFFFFF) # Bright white
# lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
# lightning_main.priority = 20
#
# # Secondary lightning - partial strip
# animation lightning_partial = pulse_position_animation(
# #FFFFAA, # Slightly yellow white
# 30, # center position
# 20, # covers part of strip
# 5 # soft edges
# )
# lightning_partial.priority = 15
# lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
#
# # Add blue afterglow
# animation afterglow = solid(#4444FF) # Blue glow
# afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
# afterglow.priority = 10
#
# # Distant thunder (dim flashes)
# animation distant_flash = twinkle_animation(
# #666699, # Dim blue-white
# 4, # density (few flashes)
# 300ms # twinkle speed (medium duration)
# )
# distant_flash.priority = 5
#
# # Start all animations
# run storm_bg
# run lightning_main
# run lightning_partial
# run afterglow
# run distant_flash
import animation
# Lightning Storm - Random lightning flashes
# Dark stormy background with bright lightning
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark stormy background with subtle purple/blue
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)
# 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
animation.global('lightning_main_').priority = 20
# Secondary lightning - partial strip
var lightning_partial_ = animation.pulse_position_animation(0xFFFFFFAA, 30, 20, 5)
animation.global('lightning_partial_').priority = 15
animation.global('lightning_partial_').opacity = animation.square(0, 200, 120, 4) # 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
animation.global('afterglow_').priority = 10
# Distant thunder (dim flashes)
var distant_flash_ = animation.twinkle_animation(0xFF666699, 4, 300)
animation.global('distant_flash_').priority = 5
# Start all animations
# Start all animations/sequences
if global.contains('sequence_storm_bg')
var seq_manager = global.sequence_storm_bg()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('storm_bg_'))
end
if global.contains('sequence_lightning_main')
var seq_manager = global.sequence_lightning_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lightning_main_'))
end
if global.contains('sequence_lightning_partial')
var seq_manager = global.sequence_lightning_partial()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('lightning_partial_'))
end
if global.contains('sequence_afterglow')
var seq_manager = global.sequence_afterglow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('afterglow_'))
end
if global.contains('sequence_distant_flash')
var seq_manager = global.sequence_distant_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('distant_flash_'))
end
engine.start()

View File

@ -0,0 +1,123 @@
# Generated Berry code from Animation DSL
# Source: matrix_rain.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:
# # Matrix Rain - Digital rain effect
# # Green cascading code like The Matrix
#
# strip length 60
#
# # Dark background
# color matrix_bg = #000000
# animation background = solid(matrix_bg)
#
# # Define matrix green palette
# palette matrix_greens = [
# (0, #000000), # Black
# (64, #003300), # Dark green
# (128, #006600), # Medium green
# (192, #00AA00), # Bright green
# (255, #00FF00) # Neon green
# ]
#
# # Create multiple cascading streams
# pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
# animation stream1 = comet_animation(
# stream1_pattern, # color source
# 15, # long tail
# 1.5s # speed
# )
# stream1.priority = 10
#
# pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
# animation stream2 = comet_animation(
# stream2_pattern, # color source
# 12, # medium tail
# 2.2s # different speed
# )
# stream2.priority = 8
#
# pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
# animation stream3 = comet_animation(
# stream3_pattern, # color source
# 10, # shorter tail
# 1.8s # another speed
# )
# stream3.priority = 6
#
# # Add random bright flashes (like code highlights)
# animation code_flash = twinkle_animation(
# #00FFAA, # Bright cyan-green
# 3, # density (few flashes)
# 150ms # twinkle speed (quick flash)
# )
# code_flash.priority = 20
#
# # Start all animations
# run background
# run stream1
# run stream2
# run stream3
# run code_flash
import animation
# Matrix Rain - Digital rain effect
# Green cascading code like The Matrix
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark background
var matrix_bg_ = 0xFF000000
var background_ = animation.solid(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)
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)
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)
animation.global('stream3_').priority = 6
# Add random bright flashes (like code highlights)
var code_flash_ = animation.twinkle_animation(0xFF00FFAA, 3, 150)
animation.global('code_flash_').priority = 20
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stream1')
var seq_manager = global.sequence_stream1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream1_'))
end
if global.contains('sequence_stream2')
var seq_manager = global.sequence_stream2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream2_'))
end
if global.contains('sequence_stream3')
var seq_manager = global.sequence_stream3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stream3_'))
end
if global.contains('sequence_code_flash')
var seq_manager = global.sequence_code_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('code_flash_'))
end
engine.start()

View File

@ -0,0 +1,140 @@
# Generated Berry code from Animation DSL
# Source: meteor_shower.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:
# # Meteor Shower - Multiple meteors with trails
# # Fast moving bright objects with fading trails
#
# strip length 60
#
# # Dark space background
# color space_bg = #000011
# animation background = solid(space_bg)
#
# # Multiple meteors with different speeds and colors
# animation meteor1 = comet_animation(
# #FFFFFF, # Bright white
# 12, # long trail
# 1.5s # fast speed
# )
# meteor1.priority = 15
#
# animation meteor2 = comet_animation(
# #FFAA00, # Orange
# 10, # medium trail
# 2s # medium speed
# )
# meteor2.priority = 12
#
# animation meteor3 = comet_animation(
# #AAAAFF, # Blue-white
# 8, # shorter trail
# 1.8s # fast speed
# )
# meteor3.priority = 10
#
# animation meteor4 = comet_animation(
# #FFAAAA, # Pink-white
# 14, # long trail
# 2.5s # slower speed
# )
# meteor4.priority = 8
#
# # Add distant stars
# animation stars = twinkle_animation(
# #CCCCCC, # Dim white
# 12, # density (many stars)
# 2s # twinkle speed (slow twinkle)
# )
# stars.priority = 5
#
# # Add occasional bright flash (meteor explosion)
# animation meteor_flash = twinkle_animation(
# #FFFFFF, # Bright white
# 1, # density (single flash)
# 100ms # twinkle speed (very quick)
# )
# meteor_flash.priority = 25
#
# # Start all animations
# run background
# run stars
# run meteor1
# run meteor2
# run meteor3
# run meteor4
# run meteor_flash
import animation
# Meteor Shower - Multiple meteors with trails
# Fast moving bright objects with fading trails
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark space background
var space_bg_ = 0xFF000011
var background_ = animation.solid(animation.global('space_bg_', 'space_bg'))
# Multiple meteors with different speeds and colors
var meteor1_ = animation.comet_animation(0xFFFFFFFF, 12, 1500)
animation.global('meteor1_').priority = 15
var meteor2_ = animation.comet_animation(0xFFFFAA00, 10, 2000)
animation.global('meteor2_').priority = 12
var meteor3_ = animation.comet_animation(0xFFAAAAFF, 8, 1800)
animation.global('meteor3_').priority = 10
var meteor4_ = animation.comet_animation(0xFFFFAAAA, 14, 2500)
animation.global('meteor4_').priority = 8
# Add distant stars
var stars_ = animation.twinkle_animation(0xFFCCCCCC, 12, 2000)
animation.global('stars_').priority = 5
# Add occasional bright flash (meteor explosion)
var meteor_flash_ = animation.twinkle_animation(0xFFFFFFFF, 1, 100)
animation.global('meteor_flash_').priority = 25
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
if global.contains('sequence_meteor1')
var seq_manager = global.sequence_meteor1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor1_'))
end
if global.contains('sequence_meteor2')
var seq_manager = global.sequence_meteor2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor2_'))
end
if global.contains('sequence_meteor3')
var seq_manager = global.sequence_meteor3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor3_'))
end
if global.contains('sequence_meteor4')
var seq_manager = global.sequence_meteor4()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor4_'))
end
if global.contains('sequence_meteor_flash')
var seq_manager = global.sequence_meteor_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('meteor_flash_'))
end
engine.start()

View File

@ -0,0 +1,140 @@
# Generated Berry code from Animation DSL
# Source: neon_glow.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:
# # Neon Glow - Electric neon tube effect
# # Bright saturated colors with flickering
#
# strip length 60
#
# # Define neon colors
# palette neon_colors = [
# (0, #FF0080), # Hot pink
# (85, #00FF80), # Neon green
# (170, #8000FF), # Electric purple
# (255, #FF8000) # Neon orange
# ]
#
# # Main neon glow with color cycling
# animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
#
# # Add electrical flickering
# neon_main.opacity = smooth(220, 255, 200ms)
#
# # Add occasional electrical surge
# animation neon_surge = solid(#FFFFFF) # White surge
# neon_surge.opacity = square(0, 255, 50ms, 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
# )
# segment1.priority = 10
#
# animation segment2 = pulse_position_animation(
# segment_pattern, # color source
# 24, # position
# 12, # segment length
# 1 # sharp edges
# )
# segment2.priority = 10
#
# animation segment3 = pulse_position_animation(
# segment_pattern, # color source
# 42, # position
# 12, # segment length
# 1 # sharp edges
# )
# segment3.priority = 10
#
# # Add electrical arcing between segments
# animation arc_sparkles = twinkle_animation(
# #AAAAFF, # Electric blue
# 4, # density (few arcs)
# 100ms # twinkle speed (quick arcs)
# )
# arc_sparkles.priority = 15
#
# # Start all animations
# run neon_main
# run neon_surge
# run segment1
# run segment2
# run segment3
# run arc_sparkles
import animation
# Neon Glow - Electric neon tube effect
# Bright saturated colors with flickering
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define neon colors
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)
# Add electrical flickering
animation.global('neon_main_').opacity = animation.smooth(220, 255, 200)
# 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
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)
animation.global('segment1_').priority = 10
var segment2_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 24, 12, 1)
animation.global('segment2_').priority = 10
var segment3_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 42, 12, 1)
animation.global('segment3_').priority = 10
# Add electrical arcing between segments
var arc_sparkles_ = animation.twinkle_animation(0xFFAAAAFF, 4, 100)
animation.global('arc_sparkles_').priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_neon_main')
var seq_manager = global.sequence_neon_main()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('neon_main_'))
end
if global.contains('sequence_neon_surge')
var seq_manager = global.sequence_neon_surge()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('neon_surge_'))
end
if global.contains('sequence_segment1')
var seq_manager = global.sequence_segment1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment1_'))
end
if global.contains('sequence_segment2')
var seq_manager = global.sequence_segment2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment2_'))
end
if global.contains('sequence_segment3')
var seq_manager = global.sequence_segment3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('segment3_'))
end
if global.contains('sequence_arc_sparkles')
var seq_manager = global.sequence_arc_sparkles()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('arc_sparkles_'))
end
engine.start()

View File

@ -0,0 +1,109 @@
# Generated Berry code from Animation DSL
# Source: ocean_waves.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:
# # Ocean Waves - Blue-green wave simulation
# # Flowing water colors with wave motion
#
# strip length 60
#
# # Define ocean color palette
# palette ocean_colors = [
# (0, #000080), # Deep blue
# (64, #0040C0), # Ocean blue
# (128, #0080FF), # Light blue
# (192, #40C0FF), # Cyan
# (255, #80FFFF) # Light cyan
# ]
#
# # Base ocean animation with slow color cycling
# animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 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
# )
# wave1.priority = 10
# wave1.pos = sawtooth(0, 48, 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
# )
# wave2.priority = 8
# wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
#
# # Add foam sparkles
# animation foam = twinkle_animation(
# #FFFFFF, # White foam
# 6, # density (sparkle count)
# 300ms # twinkle speed (quick sparkles)
# )
# foam.priority = 15
#
# # Start all animations
# run ocean_base
# run wave1
# run wave2
# run foam
import animation
# Ocean Waves - Blue-green wave simulation
# Flowing water colors with wave motion
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define ocean color palette
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)
# 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)
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)
animation.global('wave2_').priority = 8
animation.global('wave2_').pos = animation.sawtooth(52, 8, 7000) # Opposite direction
# Add foam sparkles
var foam_ = animation.twinkle_animation(0xFFFFFFFF, 6, 300)
animation.global('foam_').priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_ocean_base')
var seq_manager = global.sequence_ocean_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('ocean_base_'))
end
if global.contains('sequence_wave1')
var seq_manager = global.sequence_wave1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('wave1_'))
end
if global.contains('sequence_wave2')
var seq_manager = global.sequence_wave2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('wave2_'))
end
if global.contains('sequence_foam')
var seq_manager = global.sequence_foam()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('foam_'))
end
engine.start()

View File

@ -0,0 +1,85 @@
# Generated Berry code from Animation DSL
# Source: palette_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:
# # Palette Demo - Shows how to use custom palettes in DSL
# # This demonstrates the new palette syntax
#
# strip length 30
#
# # Define a fire palette
# palette fire_colors = [
# (0, #000000), # Black
# (64, #800000), # Dark red
# (128, #FF0000), # Red
# (192, #FF8000), # Orange
# (255, #FFFF00) # Yellow
# ]
#
# # Define an ocean palette
# palette ocean_colors = [
# (0, #000080), # Navy blue
# (64, #0000FF), # Blue
# (128, #00FFFF), # Cyan
# (192, #00FF80), # Spring green
# (255, #008000) # Green
# ]
#
# # Create animations using the palettes
# animation fire_anim = rich_palette_animation(fire_colors, 5s)
#
# animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
#
# # Sequence to show both palettes
# sequence palette_demo {
# play fire_anim for 10s
# wait 1s
# play ocean_anim for 10s
# wait 1s
# repeat 2 times:
# play fire_anim for 3s
# play ocean_anim for 3s
# }
#
# run palette_demo
import animation
# Palette Demo - Shows how to use custom palettes in DSL
# This demonstrates the new palette syntax
var strip = global.Leds(30)
var engine = animation.create_engine(strip)
# Define a fire palette
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)
# Sequence to show both palettes
def sequence_palette_demo()
var steps = []
steps.push(animation.create_play_step(animation.global('fire_anim_'), 10000))
steps.push(animation.create_wait_step(1000))
steps.push(animation.create_play_step(animation.global('ocean_anim_'), 10000))
steps.push(animation.create_wait_step(1000))
for repeat_i : 0..2-1
steps.push(animation.create_play_step(animation.global('fire_anim_'), 3000))
steps.push(animation.create_play_step(animation.global('ocean_anim_'), 3000))
end
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_palette_demo')
var seq_manager = global.sequence_palette_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('palette_demo_'))
end
engine.start()

View File

@ -0,0 +1,143 @@
# Generated Berry code from Animation DSL
# Source: palette_showcase.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:
# # Palette Showcase - Demonstrates all palette features
# # This example shows the full range of palette capabilities
#
# strip length 60
#
# # Example 1: Fire palette with hex colors
# palette fire_gradient = [
# (0, #000000), # Black (no fire)
# (32, #330000), # Very dark red
# (64, #660000), # Dark red
# (96, #CC0000), # Red
# (128, #FF3300), # Red-orange
# (160, #FF6600), # Orange
# (192, #FF9900), # Light orange
# (224, #FFCC00), # Yellow-orange
# (255, #FFFF00) # Bright yellow
# ]
#
# # Example 2: Ocean palette with named colors
# palette ocean_depths = [
# (0, black), # Deep ocean
# (64, navy), # Deep blue
# (128, blue), # Ocean blue
# (192, cyan), # Shallow water
# (255, white) # Foam/waves
# ]
#
# # Example 3: Aurora palette (from the original example)
# palette aurora_borealis = [
# (0, #000022), # Dark night sky
# (64, #004400), # Dark green
# (128, #00AA44), # Aurora green
# (192, #44AA88), # Light green
# (255, #88FFAA) # Bright aurora
# ]
#
# # Example 4: Sunset palette mixing hex and named colors
# palette sunset_sky = [
# (0, #191970), # Midnight blue
# (64, purple), # Purple twilight
# (128, #FF69B4), # Hot pink
# (192, orange), # Sunset orange
# (255, yellow) # Sun
# ]
#
# # Create animations using each palette
# animation fire_effect = rich_palette_animation(fire_gradient, 3s)
#
# animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
#
# animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
#
# animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
#
# # Sequence to showcase all palettes
# sequence palette_showcase {
# # Fire effect
# play fire_effect for 8s
# wait 1s
#
# # Ocean waves
# play ocean_waves for 8s
# wait 1s
#
# # Aurora borealis
# play aurora_lights for 8s
# wait 1s
#
# # Sunset
# play sunset_glow for 8s
# wait 1s
#
# # Quick cycle through all
# repeat 3 times:
# play fire_effect for 2s
# play ocean_waves for 2s
# play aurora_lights for 2s
# play sunset_glow for 2s
# }
#
# run palette_showcase
import animation
# Palette Showcase - Demonstrates all palette features
# This example shows the full range of palette capabilities
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Example 1: Fire palette with hex colors
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")
# Example 3: Aurora palette (from the original example)
var aurora_borealis_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FFAA")
# 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)
# Sequence to showcase all palettes
def sequence_palette_showcase()
var steps = []
# Fire effect
steps.push(animation.create_play_step(animation.global('fire_effect_'), 8000))
steps.push(animation.create_wait_step(1000))
# Ocean waves
steps.push(animation.create_play_step(animation.global('ocean_waves_'), 8000))
steps.push(animation.create_wait_step(1000))
# Aurora borealis
steps.push(animation.create_play_step(animation.global('aurora_lights_'), 8000))
steps.push(animation.create_wait_step(1000))
# Sunset
steps.push(animation.create_play_step(animation.global('sunset_glow_'), 8000))
steps.push(animation.create_wait_step(1000))
# Quick cycle through all
for repeat_i : 0..3-1
steps.push(animation.create_play_step(animation.global('fire_effect_'), 2000))
steps.push(animation.create_play_step(animation.global('ocean_waves_'), 2000))
steps.push(animation.create_play_step(animation.global('aurora_lights_'), 2000))
steps.push(animation.create_play_step(animation.global('sunset_glow_'), 2000))
end
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_palette_showcase')
var seq_manager = global.sequence_palette_showcase()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('palette_showcase_'))
end
engine.start()

View File

@ -0,0 +1,126 @@
# 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 strip = global.Leds(30)
var engine = animation.create_engine(strip)
# 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

@ -0,0 +1,118 @@
# Generated Berry code from Animation DSL
# Source: plasma_wave.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:
# # Plasma Wave - Smooth flowing plasma colors
# # Continuous color waves like plasma display
#
# strip length 60
#
# # Define plasma color palette with smooth transitions
# palette plasma_colors = [
# (0, #FF0080), # Magenta
# (51, #FF8000), # Orange
# (102, #FFFF00), # Yellow
# (153, #80FF00), # Yellow-green
# (204, #00FF80), # Cyan-green
# (255, #0080FF) # Blue
# ]
#
# # Base plasma animation with medium speed
# animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 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
# )
# plasma_wave1.priority = 10
# plasma_wave1.pos = smooth(0, 40, 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
# )
# plasma_wave2.priority = 8
# plasma_wave2.pos = smooth(45, 15, 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
# )
# plasma_wave3.priority = 12
# plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
#
# # Add subtle intensity variation
# plasma_base.opacity = smooth(150, 255, 12s)
#
# # Start all animations
# run plasma_base
# run plasma_wave1
# run plasma_wave2
# run plasma_wave3
import animation
# Plasma Wave - Smooth flowing plasma colors
# Continuous color waves like plasma display
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define plasma color palette with smooth transitions
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)
# 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)
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)
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)
animation.global('plasma_wave3_').priority = 12
animation.global('plasma_wave3_').pos = animation.smooth(20, 50, 6000) # Different speed
# Add subtle intensity variation
animation.global('plasma_base_').opacity = animation.smooth(150, 255, 12000)
# Start all animations
# Start all animations/sequences
if global.contains('sequence_plasma_base')
var seq_manager = global.sequence_plasma_base()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_base_'))
end
if global.contains('sequence_plasma_wave1')
var seq_manager = global.sequence_plasma_wave1()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave1_'))
end
if global.contains('sequence_plasma_wave2')
var seq_manager = global.sequence_plasma_wave2()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave2_'))
end
if global.contains('sequence_plasma_wave3')
var seq_manager = global.sequence_plasma_wave3()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('plasma_wave3_'))
end
engine.start()

View File

@ -0,0 +1,87 @@
# Generated Berry code from Animation DSL
# Source: police_lights.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:
# # Police Lights - Red and blue alternating flashes
# # Emergency vehicle style lighting
#
# strip length 60
#
# # Define zones for left and right halves
# set half_length = 30
#
# # Left side red flashing
# animation left_red = pulse_position_animation(
# #FF0000, # Bright red
# 15, # center of left half
# 15, # half the strip
# 2 # sharp edges
# )
# left_red.priority = 10
# left_red.opacity = square(0, 255, 400ms, 50) # 50% duty cycle
#
# # Right side blue flashing (opposite phase)
# animation right_blue = pulse_position_animation(
# #0000FF, # Bright blue
# 45, # center of right half
# 15, # half the strip
# 2 # sharp edges
# )
# right_blue.priority = 10
# right_blue.opacity = square(255, 0, 400ms, 50) # Opposite phase
#
# # Add white strobe overlay occasionally
# animation white_strobe = solid(#FFFFFF)
# white_strobe.opacity = square(0, 255, 100ms, 5) # Quick bright flashes
# white_strobe.priority = 20
#
# # Start all animations
# run left_red
# run right_blue
# run white_strobe
import animation
# Police Lights - Red and blue alternating flashes
# Emergency vehicle style lighting
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define zones for left and right halves
var half_length_ = 30
# Left side red flashing
var left_red_ = animation.pulse_position_animation(0xFFFF0000, 15, 15, 2)
animation.global('left_red_').priority = 10
animation.global('left_red_').opacity = animation.square(0, 255, 400, 50) # 50% duty cycle
# Right side blue flashing (opposite phase)
var right_blue_ = animation.pulse_position_animation(0xFF0000FF, 45, 15, 2)
animation.global('right_blue_').priority = 10
animation.global('right_blue_').opacity = animation.square(255, 0, 400, 50) # 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
animation.global('white_strobe_').priority = 20
# Start all animations
# Start all animations/sequences
if global.contains('sequence_left_red')
var seq_manager = global.sequence_left_red()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('left_red_'))
end
if global.contains('sequence_right_blue')
var seq_manager = global.sequence_right_blue()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('right_blue_'))
end
if global.contains('sequence_white_strobe')
var seq_manager = global.sequence_white_strobe()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('white_strobe_'))
end
engine.start()

View File

@ -0,0 +1,102 @@
# Generated Berry code from Animation DSL
# Source: property_assignment_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:
# # Property Assignment Demo
# # Shows how to set animation properties after creation
#
# strip length 60
#
# # Define colors
# color red_custom = #FF0000
# color blue_custom = #0000FF
# color green_custom = #00FF00
#
# # 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)
#
# # Set different opacities
# left_pulse.opacity = 255 # Full brightness
# center_pulse.opacity = 200 # Slightly dimmed
# right_pulse.opacity = 150 # More dimmed
#
# # Set priorities (higher numbers have priority)
# left_pulse.priority = 10
# center_pulse.priority = 15 # Center has highest priority
# right_pulse.priority = 5
#
# # Create a sequence that shows all three
# sequence demo {
# play left_pulse for 3s
# wait 500ms
# play center_pulse for 3s
# wait 500ms
# play right_pulse for 3s
# wait 500ms
#
# # Play all together for final effect
# repeat 3 times:
# play left_pulse for 2s
# play center_pulse for 2s
# play right_pulse for 2s
# wait 1s
# }
#
# run demo
import animation
# Property Assignment Demo
# Shows how to set animation properties after creation
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define colors
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)
# Set different opacities
animation.global('left_pulse_').opacity = 255 # Full brightness
animation.global('center_pulse_').opacity = 200 # Slightly dimmed
animation.global('right_pulse_').opacity = 150 # More dimmed
# Set priorities (higher numbers have priority)
animation.global('left_pulse_').priority = 10
animation.global('center_pulse_').priority = 15 # Center has highest priority
animation.global('right_pulse_').priority = 5
# Create a sequence that shows all three
def sequence_demo()
var steps = []
steps.push(animation.create_play_step(animation.global('left_pulse_'), 3000))
steps.push(animation.create_wait_step(500))
steps.push(animation.create_play_step(animation.global('center_pulse_'), 3000))
steps.push(animation.create_wait_step(500))
steps.push(animation.create_play_step(animation.global('right_pulse_'), 3000))
steps.push(animation.create_wait_step(500))
# Play all together for final effect
for repeat_i : 0..3-1
steps.push(animation.create_play_step(animation.global('left_pulse_'), 2000))
steps.push(animation.create_play_step(animation.global('center_pulse_'), 2000))
steps.push(animation.create_play_step(animation.global('right_pulse_'), 2000))
steps.push(animation.create_wait_step(1000))
end
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
engine.start()

View File

@ -0,0 +1,39 @@
# Generated Berry code from Animation DSL
# Source: rainbow_cycle.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:
# # Rainbow Cycle - Classic WLED effect
# # Smooth rainbow colors cycling across the strip
#
# strip length 60
#
# # Create smooth rainbow cycle animation
# animation rainbow_cycle = color_cycle_animation(
# [#FF0000, #FF8000, #FFFF00, #00FF00, #0000FF, #8000FF, #FF00FF], # rainbow colors
# 5s # cycle period
# )
#
# # Start the animation
# run rainbow_cycle
import animation
# Rainbow Cycle - Classic WLED effect
# Smooth rainbow colors cycling across the strip
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Create smooth rainbow cycle animation
var rainbow_cycle_ = animation.color_cycle_animation([0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF], 5000)
# Start the animation
# Start all animations/sequences
if global.contains('sequence_rainbow_cycle')
var seq_manager = global.sequence_rainbow_cycle()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('rainbow_cycle_'))
end
engine.start()

View File

@ -0,0 +1,240 @@
#!/bin/bash
# Test runner for successfully compiled DSL examples
BERRY_CMD="./berry -s -g -m lib/libesp32/berry_animation"
COMPILED_DIR="compiled"
echo "Testing successfully compiled DSL examples..."
echo "============================================="
SUCCESS_COUNT=0
TOTAL_COUNT=0
echo -n "Testing aurora_borealis.be... "
if $BERRY_CMD "$COMPILED_DIR/aurora_borealis.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing breathing_colors.be... "
if $BERRY_CMD "$COMPILED_DIR/breathing_colors.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing candy_cane.be... "
if $BERRY_CMD "$COMPILED_DIR/candy_cane.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing christmas_tree.be... "
if $BERRY_CMD "$COMPILED_DIR/christmas_tree.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing comet_chase.be... "
if $BERRY_CMD "$COMPILED_DIR/comet_chase.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing disco_strobe.be... "
if $BERRY_CMD "$COMPILED_DIR/disco_strobe.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing fire_flicker.be... "
if $BERRY_CMD "$COMPILED_DIR/fire_flicker.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing heartbeat_pulse.be... "
if $BERRY_CMD "$COMPILED_DIR/heartbeat_pulse.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing lava_lamp.be... "
if $BERRY_CMD "$COMPILED_DIR/lava_lamp.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing lightning_storm.be... "
if $BERRY_CMD "$COMPILED_DIR/lightning_storm.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing matrix_rain.be... "
if $BERRY_CMD "$COMPILED_DIR/matrix_rain.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing meteor_shower.be... "
if $BERRY_CMD "$COMPILED_DIR/meteor_shower.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing neon_glow.be... "
if $BERRY_CMD "$COMPILED_DIR/neon_glow.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing ocean_waves.be... "
if $BERRY_CMD "$COMPILED_DIR/ocean_waves.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing palette_demo.be... "
if $BERRY_CMD "$COMPILED_DIR/palette_demo.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing palette_showcase.be... "
if $BERRY_CMD "$COMPILED_DIR/palette_showcase.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing pattern_animation_demo.be... "
if $BERRY_CMD "$COMPILED_DIR/pattern_animation_demo.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing plasma_wave.be... "
if $BERRY_CMD "$COMPILED_DIR/plasma_wave.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing police_lights.be... "
if $BERRY_CMD "$COMPILED_DIR/police_lights.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing property_assignment_demo.be... "
if $BERRY_CMD "$COMPILED_DIR/property_assignment_demo.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing rainbow_cycle.be... "
if $BERRY_CMD "$COMPILED_DIR/rainbow_cycle.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing scanner_larson.be... "
if $BERRY_CMD "$COMPILED_DIR/scanner_larson.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing simple_palette.be... "
if $BERRY_CMD "$COMPILED_DIR/simple_palette.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing sunrise_sunset.be... "
if $BERRY_CMD "$COMPILED_DIR/sunrise_sunset.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo -n "Testing twinkle_stars.be... "
if $BERRY_CMD "$COMPILED_DIR/twinkle_stars.be" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
fi
((TOTAL_COUNT++))
echo ""
echo "Test Results: $SUCCESS_COUNT/$TOTAL_COUNT examples executed successfully"

View File

@ -0,0 +1,54 @@
#!/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

@ -0,0 +1,35 @@
#!/bin/bash
# Test runner for compiled DSL examples
# Generated automatically by compile_all_examples.sh
set -e
BERRY_CMD="./berry -s -g -m lib/libesp32/berry_animation -e 'import tasmota'"
COMPILED_DIR="anim_examples/compiled"
echo "Running compiled DSL examples..."
echo "==============================="
SUCCESS_COUNT=0
TOTAL_COUNT=0
for berry_file in "$COMPILED_DIR"/*.be; do
if [ -f "$berry_file" ]; then
filename=$(basename "$berry_file")
echo -n "Testing $filename... "
((TOTAL_COUNT++))
if eval "$BERRY_CMD \"$berry_file\"" > /dev/null 2>&1; then
echo "✓"
((SUCCESS_COUNT++))
else
echo "✗"
echo " Error details:"
eval "$BERRY_CMD \"$berry_file\"" 2>&1 | sed 's/^/ /'
fi
fi
done
echo ""
echo "Test Results: $SUCCESS_COUNT/$TOTAL_COUNT examples ran successfully"

View File

@ -0,0 +1,85 @@
# Generated Berry code from Animation DSL
# Source: scanner_larson.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:
# # Scanner (Larson) - Knight Rider style scanner
# # Red dot bouncing back and forth
#
# strip length 60
#
# # Dark background
# color scanner_bg = #110000
# animation background = solid(scanner_bg)
#
# # Main scanner pulse that bounces
# animation scanner = pulse_position_animation(
# #FF0000, # Bright red
# 2, # initial position
# 3, # pulse width
# 2 # fade region
# )
# scanner.priority = 10
#
# # Bouncing position from left to right and back
# scanner.pos = triangle(2, 57, 2s)
#
# # Add trailing glow effect
# animation scanner_trail = pulse_position_animation(
# #660000, # Dim red trail
# 2, # initial position
# 6, # wider trail
# 4 # more fade
# )
# scanner_trail.priority = 5
# scanner_trail.pos = triangle(2, 57, 2s)
# scanner_trail.opacity = 128 # Half brightness
#
# # Start all animations
# run background
# run scanner_trail
# run scanner
import animation
# Scanner (Larson) - Knight Rider style scanner
# Red dot bouncing back and forth
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark background
var scanner_bg_ = 0xFF110000
var background_ = animation.solid(animation.global('scanner_bg_', 'scanner_bg'))
# Main scanner pulse that bounces
var scanner_ = animation.pulse_position_animation(0xFFFF0000, 2, 3, 2)
animation.global('scanner_').priority = 10
# Bouncing position from left to right and back
animation.global('scanner_').pos = animation.triangle(2, 57, 2000)
# Add trailing glow effect
var scanner_trail_ = animation.pulse_position_animation(0xFF660000, 2, 6, 4)
animation.global('scanner_trail_').priority = 5
animation.global('scanner_trail_').pos = animation.triangle(2, 57, 2000)
animation.global('scanner_trail_').opacity = 128 # Half brightness
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_scanner_trail')
var seq_manager = global.sequence_scanner_trail()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('scanner_trail_'))
end
if global.contains('sequence_scanner')
var seq_manager = global.sequence_scanner()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('scanner_'))
end
engine.start()

View File

@ -0,0 +1,58 @@
# Generated Berry code from Animation DSL
# Source: simple_palette.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:
# # Simple Palette Example
# # Demonstrates basic palette usage in the DSL
#
# strip length 20
#
# # Define a simple rainbow palette
# palette rainbow = [
# (0, red),
# (64, orange),
# (128, yellow),
# (192, green),
# (255, blue)
# ]
#
# # Create an animation using the palette
# animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
#
# # Simple sequence
# sequence demo {
# play rainbow_cycle for 15s
# }
#
# run demo
import animation
# Simple Palette Example
# Demonstrates basic palette usage in the DSL
var strip = global.Leds(20)
var engine = animation.create_engine(strip)
# Define a simple rainbow palette
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)
# Simple sequence
def sequence_demo()
var steps = []
steps.push(animation.create_play_step(animation.global('rainbow_cycle_'), 15000))
var seq_manager = animation.SequenceManager(engine)
seq_manager.start_sequence(steps)
return seq_manager
end
# Start all animations/sequences
if global.contains('sequence_demo')
var seq_manager = global.sequence_demo()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('demo_'))
end
engine.start()

View File

@ -0,0 +1,117 @@
# Generated Berry code from Animation DSL
# Source: sunrise_sunset.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:
# # Sunrise Sunset - Warm color transition
# # Gradual transition from night to day colors
#
# strip length 60
#
# # Define time-of-day color palette
# palette daylight_colors = [
# (0, #000011), # Night - dark blue
# (32, #001133), # Pre-dawn
# (64, #FF4400), # Sunrise orange
# (96, #FFAA00), # Morning yellow
# (128, #FFFF88), # Midday bright
# (160, #FFAA44), # Afternoon
# (192, #FF6600), # Sunset orange
# (224, #AA2200), # Dusk red
# (255, #220011) # Night - dark red
# ]
#
# # Main daylight cycle - very slow transition
# animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
#
# # Add sun position effect - bright spot that moves
# animation sun_position = pulse_position_animation(
# #FFFFAA, # Bright yellow sun
# 5, # initial position
# 8, # sun 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
#
# # Add atmospheric glow around sun
# animation sun_glow = pulse_position_animation(
# #FFCC88, # Warm glow
# 5, # initial position
# 16, # larger glow
# 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
#
# # Add twinkling stars during night phases
# animation stars = twinkle_animation(
# #FFFFFF, # White stars
# 6, # density (star count)
# 1s # twinkle speed (slow twinkle)
# )
# stars.priority = 15
# stars.opacity = smooth(255, 0, 30s) # Fade out during day
#
# # Start all animations
# run daylight_cycle
# run sun_position
# run sun_glow
# run stars
import animation
# Sunrise Sunset - Warm color transition
# Gradual transition from night to day colors
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Define time-of-day color palette
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)
# Add sun position effect - bright spot that moves
var sun_position_ = animation.pulse_position_animation(0xFFFFFFAA, 5, 8, 4)
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
# Add atmospheric glow around sun
var sun_glow_ = animation.pulse_position_animation(0xFFFFCC88, 5, 16, 8)
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
# Add twinkling stars during night phases
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 6, 1000)
animation.global('stars_').priority = 15
animation.global('stars_').opacity = animation.smooth(255, 0, 30000) # Fade out during day
# Start all animations
# Start all animations/sequences
if global.contains('sequence_daylight_cycle')
var seq_manager = global.sequence_daylight_cycle()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('daylight_cycle_'))
end
if global.contains('sequence_sun_position')
var seq_manager = global.sequence_sun_position()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('sun_position_'))
end
if global.contains('sequence_sun_glow')
var seq_manager = global.sequence_sun_glow()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('sun_glow_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
engine.start()

View File

@ -0,0 +1,46 @@
#!/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

@ -0,0 +1,74 @@
# Generated Berry code from Animation DSL
# Source: twinkle_stars.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:
# # Twinkle Stars - Random sparkling white stars
# # White sparkles on dark blue background
#
# strip length 60
#
# # Dark blue background
# color night_sky = #000033
# animation background = solid(night_sky)
#
# # White twinkling stars
# animation stars = twinkle_animation(
# #FFFFFF, # White stars
# 8, # density (number of stars)
# 500ms # twinkle speed (twinkle duration)
# )
# stars.priority = 10
#
# # Add occasional bright flash
# animation bright_flash = twinkle_animation(
# #FFFFAA, # Bright yellow-white
# 2, # density (fewer bright flashes)
# 300ms # twinkle speed (quick flash)
# )
# bright_flash.priority = 15
#
# # Start all animations
# run background
# run stars
# run bright_flash
import animation
# Twinkle Stars - Random sparkling white stars
# White sparkles on dark blue background
var strip = global.Leds(60)
var engine = animation.create_engine(strip)
# Dark blue background
var night_sky_ = 0xFF000033
var background_ = animation.solid(animation.global('night_sky_', 'night_sky'))
# White twinkling stars
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 8, 500)
animation.global('stars_').priority = 10
# Add occasional bright flash
var bright_flash_ = animation.twinkle_animation(0xFFFFFFAA, 2, 300)
animation.global('bright_flash_').priority = 15
# Start all animations
# Start all animations/sequences
if global.contains('sequence_background')
var seq_manager = global.sequence_background()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('background_'))
end
if global.contains('sequence_stars')
var seq_manager = global.sequence_stars()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('stars_'))
end
if global.contains('sequence_bright_flash')
var seq_manager = global.sequence_bright_flash()
engine.add_sequence_manager(seq_manager)
else
engine.add_animation(animation.global('bright_flash_'))
end
engine.start()

View File

@ -0,0 +1,52 @@
# Disco Strobe - Fast colorful strobing
# Rapid color changes with strobe effects
strip length 60
# Define disco color palette
palette disco_colors = [
(0, #FF0000), # Red
(42, #FF8000), # Orange
(85, #FFFF00), # Yellow
(128, #00FF00), # Green
(170, #0000FF), # Blue
(213, #8000FF), # Purple
(255, #FF00FF) # Magenta
]
# Fast color cycling base
animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
# Add strobe effect
disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
# Add white flash overlay
animation white_flash = solid(#FFFFFF)
white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
white_flash.priority = 20
# Add colored sparkles
pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
animation disco_sparkles = twinkle_animation(
sparkle_pattern, # color source
12, # density (many sparkles)
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)
)
disco_pulse.priority = 10
disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
# Start all animations
run disco_base
run white_flash
run disco_sparkles
run disco_pulse

View File

@ -0,0 +1,32 @@
# Fire Flicker - Realistic fire simulation
# Warm colors with random flickering intensity
strip length 60
# Define fire palette from black to yellow
palette fire_colors = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF4500), # Orange red
(255, #FFFF00) # Yellow
]
# Create base fire animation with palette
animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
# Add flickering effect with random intensity changes
fire_base.opacity = smooth(180, 255, 800ms)
# Add subtle position variation for more realism
pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
animation fire_flicker = twinkle_animation(
flicker_pattern, # color source
12, # density (number of flickers)
200ms # twinkle speed (flicker duration)
)
fire_flicker.priority = 10
# Start both animations
run fire_base
run fire_flicker

View File

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

View File

@ -0,0 +1,63 @@
# Lava Lamp - Slow flowing warm colors
# Organic movement like a lava lamp
strip length 60
# Define lava colors (warm oranges and reds)
palette lava_colors = [
(0, #330000), # Dark red
(64, #660000), # Medium red
(128, #CC3300), # Bright red
(192, #FF6600), # Orange
(255, #FFAA00) # Yellow-orange
]
# Base lava animation - very slow color changes
animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 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
)
lava_blob1.priority = 10
lava_blob1.pos = smooth(9, 51, 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
)
lava_blob2.priority = 8
lava_blob2.pos = smooth(46, 14, 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
)
lava_blob3.priority = 6
lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
# Add subtle heat shimmer effect
pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
animation heat_shimmer = twinkle_animation(
shimmer_pattern, # color source
6, # density (shimmer points)
1.5s # twinkle speed (slow shimmer)
)
heat_shimmer.priority = 15
# Start all animations
run lava_base
run lava_blob1
run lava_blob2
run lava_blob3
run heat_shimmer

View File

@ -0,0 +1,48 @@
# Lightning Storm - Random lightning flashes
# Dark stormy background with bright lightning
strip length 60
# Dark stormy background with subtle purple/blue
palette storm_colors = [
(0, #000011), # Very dark blue
(128, #110022), # Dark purple
(255, #220033) # Slightly lighter purple
]
animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
# Random lightning flashes - full strip
animation lightning_main = solid(#FFFFFF) # Bright white
lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
lightning_main.priority = 20
# Secondary lightning - partial strip
animation lightning_partial = pulse_position_animation(
#FFFFAA, # Slightly yellow white
30, # center position
20, # covers part of strip
5 # soft edges
)
lightning_partial.priority = 15
lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
# Add blue afterglow
animation afterglow = solid(#4444FF) # Blue glow
afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
afterglow.priority = 10
# Distant thunder (dim flashes)
animation distant_flash = twinkle_animation(
#666699, # Dim blue-white
4, # density (few flashes)
300ms # twinkle speed (medium duration)
)
distant_flash.priority = 5
# Start all animations
run storm_bg
run lightning_main
run lightning_partial
run afterglow
run distant_flash

View File

@ -0,0 +1,57 @@
# Matrix Rain - Digital rain effect
# Green cascading code like The Matrix
strip length 60
# Dark background
color matrix_bg = #000000
animation background = solid(matrix_bg)
# Define matrix green palette
palette matrix_greens = [
(0, #000000), # Black
(64, #003300), # Dark green
(128, #006600), # Medium green
(192, #00AA00), # Bright green
(255, #00FF00) # Neon green
]
# Create multiple cascading streams
pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
animation stream1 = comet_animation(
stream1_pattern, # color source
15, # long tail
1.5s # speed
)
stream1.priority = 10
pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
animation stream2 = comet_animation(
stream2_pattern, # color source
12, # medium tail
2.2s # different speed
)
stream2.priority = 8
pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
animation stream3 = comet_animation(
stream3_pattern, # color source
10, # shorter tail
1.8s # another speed
)
stream3.priority = 6
# Add random bright flashes (like code highlights)
animation code_flash = twinkle_animation(
#00FFAA, # Bright cyan-green
3, # density (few flashes)
150ms # twinkle speed (quick flash)
)
code_flash.priority = 20
# Start all animations
run background
run stream1
run stream2
run stream3
run code_flash

View File

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

View File

@ -0,0 +1,65 @@
# Neon Glow - Electric neon tube effect
# Bright saturated colors with flickering
strip length 60
# Define neon colors
palette neon_colors = [
(0, #FF0080), # Hot pink
(85, #00FF80), # Neon green
(170, #8000FF), # Electric purple
(255, #FF8000) # Neon orange
]
# Main neon glow with color cycling
animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
# Add electrical flickering
neon_main.opacity = smooth(220, 255, 200ms)
# Add occasional electrical surge
animation neon_surge = solid(#FFFFFF) # White surge
neon_surge.opacity = square(0, 255, 50ms, 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
)
segment1.priority = 10
animation segment2 = pulse_position_animation(
segment_pattern, # color source
24, # position
12, # segment length
1 # sharp edges
)
segment2.priority = 10
animation segment3 = pulse_position_animation(
segment_pattern, # color source
42, # position
12, # segment length
1 # sharp edges
)
segment3.priority = 10
# Add electrical arcing between segments
animation arc_sparkles = twinkle_animation(
#AAAAFF, # Electric blue
4, # density (few arcs)
100ms # twinkle speed (quick arcs)
)
arc_sparkles.priority = 15
# Start all animations
run neon_main
run neon_surge
run segment1
run segment2
run segment3
run arc_sparkles

View File

@ -0,0 +1,51 @@
# Ocean Waves - Blue-green wave simulation
# Flowing water colors with wave motion
strip length 60
# Define ocean color palette
palette ocean_colors = [
(0, #000080), # Deep blue
(64, #0040C0), # Ocean blue
(128, #0080FF), # Light blue
(192, #40C0FF), # Cyan
(255, #80FFFF) # Light cyan
]
# Base ocean animation with slow color cycling
animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 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
)
wave1.priority = 10
wave1.pos = sawtooth(0, 48, 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
)
wave2.priority = 8
wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
# Add foam sparkles
animation foam = twinkle_animation(
#FFFFFF, # White foam
6, # density (sparkle count)
300ms # twinkle speed (quick sparkles)
)
foam.priority = 15
# Start all animations
run ocean_base
run wave1
run wave2
run foam

View File

@ -0,0 +1,40 @@
# Palette Demo - Shows how to use custom palettes in DSL
# This demonstrates the new palette syntax
strip length 30
# Define a fire palette
palette fire_colors = [
(0, #000000), # Black
(64, #800000), # Dark red
(128, #FF0000), # Red
(192, #FF8000), # Orange
(255, #FFFF00) # Yellow
]
# Define an ocean palette
palette ocean_colors = [
(0, #000080), # Navy blue
(64, #0000FF), # Blue
(128, #00FFFF), # Cyan
(192, #00FF80), # Spring green
(255, #008000) # Green
]
# Create animations using the palettes
animation fire_anim = rich_palette_animation(fire_colors, 5s)
animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
# Sequence to show both palettes
sequence palette_demo {
play fire_anim for 10s
wait 1s
play ocean_anim for 10s
wait 1s
repeat 2 times:
play fire_anim for 3s
play ocean_anim for 3s
}
run palette_demo

View File

@ -0,0 +1,81 @@
# Palette Showcase - Demonstrates all palette features
# This example shows the full range of palette capabilities
strip length 60
# Example 1: Fire palette with hex colors
palette fire_gradient = [
(0, #000000), # Black (no fire)
(32, #330000), # Very dark red
(64, #660000), # Dark red
(96, #CC0000), # Red
(128, #FF3300), # Red-orange
(160, #FF6600), # Orange
(192, #FF9900), # Light orange
(224, #FFCC00), # Yellow-orange
(255, #FFFF00) # Bright yellow
]
# Example 2: Ocean palette with named colors
palette ocean_depths = [
(0, black), # Deep ocean
(64, navy), # Deep blue
(128, blue), # Ocean blue
(192, cyan), # Shallow water
(255, white) # Foam/waves
]
# Example 3: Aurora palette (from the original example)
palette aurora_borealis = [
(0, #000022), # Dark night sky
(64, #004400), # Dark green
(128, #00AA44), # Aurora green
(192, #44AA88), # Light green
(255, #88FFAA) # Bright aurora
]
# Example 4: Sunset palette mixing hex and named colors
palette sunset_sky = [
(0, #191970), # Midnight blue
(64, purple), # Purple twilight
(128, #FF69B4), # Hot pink
(192, orange), # Sunset orange
(255, yellow) # Sun
]
# Create animations using each palette
animation fire_effect = rich_palette_animation(fire_gradient, 3s)
animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
# Sequence to showcase all palettes
sequence palette_showcase {
# Fire effect
play fire_effect for 8s
wait 1s
# Ocean waves
play ocean_waves for 8s
wait 1s
# Aurora borealis
play aurora_lights for 8s
wait 1s
# Sunset
play sunset_glow for 8s
wait 1s
# Quick cycle through all
repeat 3 times:
play fire_effect for 2s
play ocean_waves for 2s
play aurora_lights for 2s
play sunset_glow for 2s
}
run palette_showcase

View File

@ -0,0 +1,57 @@
# 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

@ -0,0 +1,57 @@
# Plasma Wave - Smooth flowing plasma colors
# Continuous color waves like plasma display
strip length 60
# Define plasma color palette with smooth transitions
palette plasma_colors = [
(0, #FF0080), # Magenta
(51, #FF8000), # Orange
(102, #FFFF00), # Yellow
(153, #80FF00), # Yellow-green
(204, #00FF80), # Cyan-green
(255, #0080FF) # Blue
]
# Base plasma animation with medium speed
animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 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
)
plasma_wave1.priority = 10
plasma_wave1.pos = smooth(0, 40, 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
)
plasma_wave2.priority = 8
plasma_wave2.pos = smooth(45, 15, 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
)
plasma_wave3.priority = 12
plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
# Add subtle intensity variation
plasma_base.opacity = smooth(150, 255, 12s)
# Start all animations
run plasma_base
run plasma_wave1
run plasma_wave2
run plasma_wave3

View File

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

View File

@ -0,0 +1,43 @@
# Property Assignment Demo
# Shows how to set animation properties after creation
strip length 60
# Define colors
color red_custom = #FF0000
color blue_custom = #0000FF
color green_custom = #00FF00
# 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)
# Set different opacities
left_pulse.opacity = 255 # Full brightness
center_pulse.opacity = 200 # Slightly dimmed
right_pulse.opacity = 150 # More dimmed
# Set priorities (higher numbers have priority)
left_pulse.priority = 10
center_pulse.priority = 15 # Center has highest priority
right_pulse.priority = 5
# Create a sequence that shows all three
sequence demo {
play left_pulse for 3s
wait 500ms
play center_pulse for 3s
wait 500ms
play right_pulse for 3s
wait 500ms
# Play all together for final effect
repeat 3 times:
play left_pulse for 2s
play center_pulse for 2s
play right_pulse for 2s
wait 1s
}
run demo

View File

@ -0,0 +1,13 @@
# Rainbow Cycle - Classic WLED effect
# Smooth rainbow colors cycling across the strip
strip length 60
# Create smooth rainbow cycle animation
animation rainbow_cycle = color_cycle_animation(
[#FF0000, #FF8000, #FFFF00, #00FF00, #0000FF, #8000FF, #FF00FF], # rainbow colors
5s # cycle period
)
# Start the animation
run rainbow_cycle

View File

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

View File

@ -0,0 +1,23 @@
# Simple Palette Example
# Demonstrates basic palette usage in the DSL
strip length 20
# Define a simple rainbow palette
palette rainbow = [
(0, red),
(64, orange),
(128, yellow),
(192, green),
(255, blue)
]
# Create an animation using the palette
animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
# Simple sequence
sequence demo {
play rainbow_cycle for 15s
}
run demo

View File

@ -0,0 +1,57 @@
# Sunrise Sunset - Warm color transition
# Gradual transition from night to day colors
strip length 60
# Define time-of-day color palette
palette daylight_colors = [
(0, #000011), # Night - dark blue
(32, #001133), # Pre-dawn
(64, #FF4400), # Sunrise orange
(96, #FFAA00), # Morning yellow
(128, #FFFF88), # Midday bright
(160, #FFAA44), # Afternoon
(192, #FF6600), # Sunset orange
(224, #AA2200), # Dusk red
(255, #220011) # Night - dark red
]
# Main daylight cycle - very slow transition
animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
# Add sun position effect - bright spot that moves
animation sun_position = pulse_position_animation(
#FFFFAA, # Bright yellow sun
5, # initial position
8, # sun 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
# Add atmospheric glow around sun
animation sun_glow = pulse_position_animation(
#FFCC88, # Warm glow
5, # initial position
16, # larger glow
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
# Add twinkling stars during night phases
animation stars = twinkle_animation(
#FFFFFF, # White stars
6, # density (star count)
1s # twinkle speed (slow twinkle)
)
stars.priority = 15
stars.opacity = smooth(255, 0, 30s) # Fade out during day
# Start all animations
run daylight_cycle
run sun_position
run sun_glow
run stars

View File

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

View File

@ -0,0 +1,733 @@
# 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)
- Returns: `PatternAnimation` instance
```berry
var red = animation.solid(0xFFFF0000)
var blue = animation.solid(0xFF0000FF, 10, 5000, true, 200, "blue_anim")
```
**`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
- **period_ms**: Breathing period in milliseconds
- Returns: `BreatheAnimation` instance
```berry
var breathe_blue = animation.breathe(0xFF0000FF, 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
- **pos**: Pixel position (0-based)
- **pulse_size**: Width of pulse in pixels
- **slew_size**: Fade region size in pixels
- Returns: `PulsePositionAnimation` instance
```berry
var center_pulse = animation.pulse_position_animation(0xFFFFFFFF, 15, 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
- **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)
```
**`animation.twinkle_animation(color, density, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates twinkling stars effect
- **color**: ARGB color value
- **density**: Number of twinkling pixels
- **speed_ms**: Twinkle speed in milliseconds
- Returns: `TwinkleAnimation` instance
```berry
var stars = animation.twinkle_animation(0xFFFFFFFF, 5, 500)
```
### Fire and Natural Effects
**`animation.fire_animation(intensity=200, speed_ms=100, priority=0, duration=0, loop=false, opacity=255, name="")`**
- Creates realistic fire simulation
- **intensity**: Fire intensity (0-255)
- **speed_ms**: Animation speed in milliseconds
- Returns: `FireAnimation` instance
```berry
var fire = animation.fire_animation(180, 150)
```
### 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
- Returns: `NoiseAnimation` instance
**`animation.noise_fractal(color_source, scale, speed, octaves, strip_length, priority)`**
- Creates multi-octave fractal noise
- **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
- 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
- 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
- Returns: `WaveAnimation` instance
**`animation.wave_custom(color_source, wave_type, amplitude, frequency, strip_length, priority)`**
- Creates custom wave with specified type
- **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
- Returns: `OscillatorValueProvider`
**`animation.ramp(start, end, period_ms)`**
- 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,568 @@
# Examples
Curated examples showcasing the Tasmota Berry Animation Framework capabilities.
## Basic Examples
### 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:**
```dsl
color red = #FF0000
animation solid_red = solid(red)
run solid_red
```
### 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 Version:**
```dsl
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 3s, 20%, 100%)
run pulse_blue
```
### 3. Breathing Effect
**DSL:**
```dsl
color soft_white = #C0C0C0
animation breathing = breathe(soft_white, 4s)
run breathing
```
## Color and Palette Examples
### 4. Fire Effect
**DSL:**
```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)
run rainbow_cycle
```
### 6. Ocean Waves
**DSL:**
```dsl
palette ocean = [
(0, navy), # Deep ocean
(64, blue), # Ocean blue
(128, cyan), # Shallow water
(192, #87CEEB), # Sky blue
(255, white) # Foam
]
animation ocean_waves = rich_palette_animation(ocean, 6s, smooth, 200)
run ocean_waves
```
## Position-Based Examples
### 7. Center Pulse
**DSL:**
```dsl
strip length 60
color white = #FFFFFF
# Pulse at center position
animation center_pulse = pulse_position_animation(white, 30, 5, 3)
run center_pulse
```
### 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
}
run rgb_show
```
### 14. Sunrise Sequence
**DSL:**
```dsl
# Define sunrise colors
color deep_blue = #000080
color purple = #800080
color pink = #FF69B4
color orange = #FFA500
color yellow = #FFFF00
# 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)
# 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
}
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:**
```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
]
# 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
```
### 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:**
```dsl
# Define single palette for multiple uses
palette shared_rainbow = [
(0, red), (51, orange), (102, yellow),
(153, green), (204, blue), (255, violet)
]
# 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)
# Use in sequence to avoid simultaneous memory usage
sequence efficient_show {
play fast_rainbow for 15s
wait 1s
play slow_rainbow for 20s
}
run efficient_show
```
## Tips for Creating Your Own Examples
### 1. Start Simple
Begin with basic solid colors and simple pulses before adding complexity.
### 2. Use Meaningful Names
```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%)
```
### 3. Comment Your Code
```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
]
```
### 4. Test Incrementally
Build complex animations step by step:
1. Test basic colors
2. Add simple effects
3. Combine with sequences
4. Add interactivity
### 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
## 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
Experiment with these examples and create your own amazing LED animations! 🎨✨

View File

@ -0,0 +1,231 @@
# 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)
├── 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 (`compiled/`)
Berry code generated from DSL examples (for reference):
```
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 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

@ -0,0 +1,253 @@
# Quick Start Guide
Get up and running with the Tasmota Berry Animation Framework in 5 minutes!
## Prerequisites
- Tasmota device with Berry support
- Addressable LED strip (WS2812, SK6812, etc.)
- Basic familiarity with Tasmota console
## Step 1: Basic Setup
### Import the Framework
```berry
import animation
```
### 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%)
# Run it
run pulse_red
```
### Load DSL Animation
```berry
import animation
var strip = Leds(30)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
# Load from string
var dsl_code = '''
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 2s, 30%, 100%)
run pulse_blue
'''
runtime.load_dsl(dsl_code)
```
## Step 4: Color Palettes
Palettes create smooth color transitions:
```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 palette animation
animation sunset_glow = rich_palette_animation(sunset, 5s, smooth, 200)
run sunset_glow
```
## Step 5: 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%)
sequence rgb_show {
play red_pulse for 3s
wait 500ms
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
play blue_pulse for 1s
}
run rgb_show
```
## Step 6: Interactive Animations
Add event handling for interactive effects:
```dsl
color white = #FFFFFF
color red = #FF0000
animation flash_white = solid(white)
animation normal_red = solid(red)
# Flash white when button pressed
on button_press: flash_white
# Main animation
run normal_red
```
## 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_effect = rich_palette_animation(fire, 2s, smooth, 255)
run fire_effect
```
### Rainbow Cycle
```dsl
palette rainbow = [
(0, red), (42, orange), (84, yellow),
(126, green), (168, blue), (210, indigo), (255, violet)
]
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
```
## Tips for Success
### 1. Start Simple
Begin with solid colors and basic pulses before moving to complex effects.
### 2. Use the DSL
The DSL is much easier than writing Berry code directly.
### 3. Test Incrementally
Add one animation at a time and test before adding complexity.
### 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()
# Check if animation was added
print(engine.size()) # Should be > 0
```
### 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
```
### 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)
end
```
### 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
```
## 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
Happy animating! 🎨✨

View File

@ -0,0 +1,599 @@
# Troubleshooting Guide
Common issues and solutions for the Tasmota Berry Animation Framework.
## Installation Issues
### Framework Not Found
**Problem:** `import animation` fails with "module not found"
**Solutions:**
1. **Check Module Path:**
```berry
# Verify the animation module exists
import sys
print(sys.path())
```
2. **Set Module Path:**
```bash
berry -m lib/libesp32/berry_animation
```
3. **Verify File Structure:**
```
lib/libesp32/berry_animation/
├── animation.be # Main module file
├── core/ # Core classes
├── effects/ # Animation effects
└── ...
```
### Missing Dependencies
**Problem:** Errors about missing `tasmota` or `Leds` classes
**Solutions:**
1. **For Tasmota Environment:**
- Ensure you're running on actual Tasmota firmware
- Check that Berry support is enabled
2. **For Development Environment:**
```berry
# Mock Tasmota for testing
if !global.contains("tasmota")
global.tasmota = {
"millis": def() return 1000 end,
"scale_uint": def(val, from_min, from_max, to_min, to_max)
return int((val - from_min) * (to_max - to_min) / (from_max - from_min) + to_min)
end
}
end
```
## Animation Issues
### Animations Not Starting
**Problem:** Animations created but LEDs don't change
**Diagnostic Steps:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
var anim = animation.solid(0xFFFF0000)
# 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())
```
**Common Solutions:**
1. **Forgot to Start Engine:**
```berry
engine.add_animation(anim)
engine.start() # Don't forget this!
```
2. **Animation Not Added:**
```berry
# Make sure animation is added to engine
engine.add_animation(anim)
print("Animation count:", engine.size()) # Should be > 0
```
3. **Strip Not Configured:**
```berry
# Check strip configuration
var strip = Leds(30) # 30 LEDs
print("Strip created:", strip != nil)
```
### Colors Look Wrong
**Problem:** Colors appear different than expected
**Common Issues:**
1. **Missing Alpha Channel:**
```berry
# Wrong - missing alpha
var red = 0xFF0000
# Correct - with alpha channel
var red = 0xFFFF0000 # ARGB format
```
2. **Color Format Confusion:**
```berry
# 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
```
3. **Brightness Issues:**
```berry
# Check opacity settings
anim.set_opacity(255) # Full brightness
# Check pulse brightness ranges
var pulse = animation.pulse(pattern, 2000, 50, 255) # Min=50, Max=255
```
### Animations Too Fast/Slow
**Problem:** Animation timing doesn't match expectations
**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
```
2. **Adjust Periods:**
```berry
# Too fast - increase period
var slow_pulse = animation.pulse(pattern, 5000, 50, 255) # 5 seconds
# Too slow - decrease period
var fast_pulse = animation.pulse(pattern, 500, 50, 255) # 0.5 seconds
```
3. **Performance Limitations:**
```berry
# Reduce number of simultaneous animations
engine.clear() # Remove all animations
engine.add_animation(single_animation)
```
## DSL Issues
### DSL Compilation Errors
**Problem:** DSL code fails to compile
**Diagnostic Approach:**
```berry
try
var berry_code = animation.compile_dsl(dsl_source)
print("Compilation successful")
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
end
```
**Common DSL Errors:**
1. **Undefined Colors:**
```dsl
# Wrong - color not defined
animation red_anim = solid(red)
# Correct - define color first
color red = #FF0000
animation red_anim = solid(red)
```
2. **Invalid Color Format:**
```dsl
# Wrong - invalid hex format
color red = FF0000
# Correct - with # prefix
color red = #FF0000
```
3. **Missing Time Units:**
```dsl
# Wrong - no time unit
animation pulse_anim = pulse(solid(red), 2000, 50%, 100%)
# Correct - with time unit
animation pulse_anim = pulse(solid(red), 2s, 50%, 100%)
```
4. **Reserved Name Conflicts:**
```dsl
# Wrong - 'red' is a predefined color
color red = #800000
# Correct - use different name
color dark_red = #800000
```
### DSL Runtime Errors
**Problem:** DSL compiles but fails at runtime
**Common Issues:**
1. **Strip Not Initialized:**
```dsl
# Add strip declaration if needed
strip length 30
color red = #FF0000
animation red_anim = solid(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
sequence demo {
play red_anim for 3s # Use after definition
}
```
## Performance Issues
### Choppy Animations
**Problem:** Animations appear jerky or stuttering
**Solutions:**
1. **Reduce Animation Count:**
```berry
# Good - 1-3 animations
engine.clear()
engine.add_animation(main_animation)
# Avoid - too many simultaneous animations
# engine.add_animation(anim1)
# engine.add_animation(anim2)
# ... (10+ animations)
```
2. **Increase Animation Periods:**
```berry
# Smooth - longer periods
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
# Choppy - very short periods
var choppy_pulse = animation.pulse(pattern, 50, 50, 255) # 50ms
```
3. **Optimize Value Providers:**
```berry
# Efficient - reuse providers
var breathing = animation.smooth(50, 255, 2000)
var anim1 = animation.pulse(pattern1, breathing)
var anim2 = animation.pulse(pattern2, breathing) # Reuse
# Inefficient - create new providers
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000))
```
### Memory Issues
**Problem:** Out of memory errors or system crashes
**Solutions:**
1. **Clear Unused Animations:**
```berry
# Clear before adding new animations
engine.clear()
engine.add_animation(new_animation)
```
2. **Limit Palette Size:**
```dsl
# Good - reasonable palette size
palette simple_fire = [
(0, #000000),
(128, #FF0000),
(255, #FFFF00)
]
# Avoid - very large palettes
# palette huge_palette = [
# (0, color1), (1, color2), ... (255, color256)
# ]
```
3. **Use Sequences Instead of Simultaneous Animations:**
```dsl
# Memory efficient - sequential playback
sequence show {
play animation1 for 5s
play animation2 for 5s
play animation3 for 5s
}
# Memory intensive - all at once
# run animation1
# run animation2
# run animation3
```
## Event System Issues
### Events Not Triggering
**Problem:** Event handlers don't execute
**Diagnostic Steps:**
```berry
# Check if handler is registered
var handlers = animation.get_event_handlers("button_press")
print("Handler count:", size(handlers))
# Test event triggering
animation.trigger_event("test_event", {"debug": true})
```
**Solutions:**
1. **Verify Handler Registration:**
```berry
def test_handler(event_data)
print("Event triggered:", event_data)
end
var handler = animation.register_event_handler("test", test_handler, 0)
print("Handler registered:", handler != nil)
```
2. **Check Event Names:**
```berry
# Event names are case-sensitive
animation.register_event_handler("button_press", handler) # Correct
animation.trigger_event("button_press", {}) # Must match exactly
```
3. **Verify Conditions:**
```berry
def condition_func(event_data)
return event_data.contains("required_field")
end
animation.register_event_handler("event", handler, 0, condition_func)
# Event data must satisfy condition
animation.trigger_event("event", {"required_field": "value"})
```
## Hardware Issues
### LEDs Not Responding
**Problem:** Framework runs but LEDs don't light up
**Hardware Checks:**
1. **Power Supply:**
- Ensure adequate power for LED count
- Check voltage (5V for WS2812)
- Verify ground connections
2. **Wiring:**
- Data line connected to correct GPIO
- Ground connected between controller and LEDs
- Check for loose connections
3. **LED Strip:**
- Test with known working code
- Check for damaged LEDs
- Verify strip type (WS2812, SK6812, etc.)
**Software Checks:**
```berry
# Test basic LED functionality
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
```
### Wrong Colors on Hardware
**Problem:** Colors look different on actual LEDs vs. expected
**Solutions:**
1. **Color Order:**
```berry
# Some strips use different color orders
# Try different strip types in Tasmota configuration
# WS2812: RGB order
# SK6812: GRBW order
```
2. **Gamma Correction:**
```berry
# Enable gamma correction in Tasmota
# SetOption37 128 # Enable gamma correction
```
3. **Power Supply Issues:**
- Voltage drop causes color shifts
- Use adequate power supply
- Add power injection for long strips
## Debugging Techniques
### Enable Debug Mode
```berry
# Enable debug output
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
# Check generated code
try
var berry_code = animation.compile_dsl(dsl_source)
print("Generated Berry code:")
print(berry_code)
except "dsl_compilation_error" as e, msg
print("Compilation error:", msg)
end
```
### Step-by-Step Testing
```berry
# Test each component individually
print("1. Creating strip...")
var strip = Leds(30)
print("Strip created:", strip != nil)
print("2. Creating engine...")
var engine = animation.create_engine(strip)
print("Engine created:", engine != nil)
print("3. Creating animation...")
var anim = animation.solid(0xFFFF0000)
print("Animation created:", anim != nil)
print("4. Adding animation...")
engine.add_animation(anim)
print("Animation count:", engine.size())
print("5. Starting engine...")
engine.start()
print("Engine active:", engine.is_active())
```
### Monitor Performance
```berry
# Check timing
var start_time = tasmota.millis()
# ... run animation code ...
var end_time = tasmota.millis()
print("Execution time:", end_time - start_time, "ms")
# Monitor memory (if available)
import gc
print("Memory before:", gc.allocated())
# ... create animations ...
print("Memory after:", gc.allocated())
```
## Getting Help
### Information to Provide
When asking for help, include:
1. **Hardware Setup:**
- LED strip type and count
- GPIO pin used
- Power supply specifications
2. **Software Environment:**
- Tasmota version
- Berry version
- Framework version
3. **Code:**
- Complete minimal example that reproduces the issue
- Error messages (exact text)
- Expected vs. actual behavior
4. **Debugging Output:**
- Debug mode output
- Generated Berry code (for DSL issues)
- Console output
### Example Bug Report
```
**Problem:** DSL animation compiles but LEDs don't change
**Hardware:**
- 30x WS2812 LEDs on GPIO 1
- ESP32 with 5V/2A power supply
**Code:**
```dsl
color red = #FF0000
animation red_anim = solid(red)
run red_anim
```
**Error Output:**
```
DSL compilation successful
Engine created: true
Animation count: 1
Engine active: true
```
**Expected:** LEDs turn red
**Actual:** LEDs remain off
**Additional Info:**
- Basic `strip.set_pixel_color(0, 0xFFFF0000); strip.show()` works
- Tasmota 13.2.0, Berry enabled
```
This format helps identify issues quickly and provide targeted solutions.
## Prevention Tips
### Code Quality
1. **Use Try-Catch Blocks:**
```berry
try
runtime.load_dsl(dsl_code)
except .. as e, msg
print("Error:", msg)
end
```
2. **Validate Inputs:**
```berry
if type(color) == "int" && color >= 0
var anim = animation.solid(color)
else
print("Invalid color:", color)
end
```
3. **Test Incrementally:**
- Start with simple solid colors
- Add one effect at a time
- Test each change before proceeding
### Performance Best Practices
1. **Limit Complexity:**
- 1-3 simultaneous animations
- Reasonable animation periods (>1 second)
- Moderate palette sizes
2. **Resource Management:**
- Clear unused animations
- Reuse value providers
- Use sequences for complex shows
3. **Hardware Considerations:**
- Adequate power supply
- Proper wiring and connections
- Appropriate LED strip for application
Following these guidelines will help you avoid most common issues and create reliable LED animations.

View File

@ -0,0 +1,16 @@
{
"name": "Berry Animation Framework",
"version": "1.0.0",
"description": "Comprehensive LED animation framework for Tasmota Berry",
"license": "MIT",
"homepage": "https://github.com/arendst/Tasmota",
"frameworks": "arduino",
"platforms": "espressif32",
"authors": {
"name": "Tasmota Team",
"maintainer": true
},
"build": {
"flags": [ "-I$PROJECT_DIR/include", "-includetasmota_options.h" ]
}
}

View File

@ -0,0 +1,109 @@
#!/usr/bin/env -S ../berry/berry -s -g
#
# Berry solidify files
import os
import global
import solidify
import string as string2
import re
import string
import sys
sys.path().push('src') # allow to import from src/embedded
sys.path().push('src/core') # allow to import from src/embedded
# globals that need to exist to make compilation succeed
var globs = "path,ctypes_bytes_dyn,tasmota,ccronexpr,gpio,light,webclient,load,MD5,lv,light_state,udp,tcpclientasync,log,"
for g:string2.split(globs, ",")
global.(g) = nil
end
# special case to declane animation
global.animation = module("animation")
var prefix_dir = "src/"
var prefix_out = "src/solidify/"
def sort(l)
# insertion sort
for i:1..size(l)-1
var k = l[i]
var j = i
while (j > 0) && (l[j-1] > k)
l[j] = l[j-1]
j -= 1
end
l[j] = k
end
return l
end
def clean_directory(dir)
var file_list = os.listdir(dir)
for f : file_list
if f[0] == '.' continue end # ignore files starting with `.`
os.remove(dir + f)
end
end
var pattern = "#@\\s*solidify:([A-Za-z0-9_.,]+)"
def parse_file(fname, prefix_out)
if !string.endswith(fname, ".be")
print(f"Skipping: {fname}")
return
end
print("Parsing: ", fname)
var f = open(prefix_dir + fname)
var src = f.read()
f.close()
# try to compile
var compiled = compile(src)
compiled() # run the compile code to instanciate the classes and modules
# output solidified
var fname_h = string2.split(fname, '.be')[0] + '.h' # take whatever is before the first '.be'
var fout = open(prefix_out + "solidified_" + fname_h, "w")
fout.write(f"/* Solidification of {fname_h} */\n")
fout.write("/********************************************************************\\\n")
fout.write("* Generated code, don't edit *\n")
fout.write("\\********************************************************************/\n")
fout.write('#include "be_constobj.h"\n')
var directives = re.searchall(pattern, src)
# print(directives)
for directive : directives
var object_list = string2.split(directive[1], ',')
var object_name = object_list[0]
var weak = (object_list.find('weak') != nil) # do we solidify with weak strings?
var o = global
var cl_name = nil
var obj_name = nil
for subname : string2.split(object_name, '.')
o = o.(subname)
cl_name = obj_name
obj_name = subname
if (type(o) == 'class')
obj_name = 'class_' + obj_name
elif (type(o) == 'module')
obj_name = 'module_' + obj_name
end
end
solidify.dump(o, weak, fout, cl_name)
end
fout.write("/********************************************************************/\n")
fout.write("/* End of solidification */\n")
fout.close()
end
clean_directory(prefix_out)
print(f"# Output directory '{prefix_out}' cleaned")
var src_file_list = os.listdir(prefix_dir)
src_file_list = sort(src_file_list)
for src_file : src_file_list
if src_file[0] == '.' continue end
parse_file(src_file, prefix_out)
end

View File

@ -0,0 +1,58 @@
# 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

@ -0,0 +1,21 @@
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

@ -0,0 +1,215 @@
# 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

@ -0,0 +1,169 @@
# this is the entry point for animation Framework
# it imports all other modules and register in the "animation" object
#
# launch with "./berry -s -g -m lib/libesp32/berry_animation"
# import in global scope all that is needed
import global
if !global.contains("tasmota")
import tasmota
end
#@ solidify:animation,weak
var animation = module("animation")
global.animation = animation
# Version information
# Format: 0xAABBCCDD (AA=major, BB=minor, CC=patch, DD=build)
animation.VERSION = 0x00010000
# Convert version number to string format "major.minor.patch"
def animation_version_string(version_num)
if version_num == nil version_num = animation.VERSION end
var major = (version_num >> 24) & 0xFF
var minor = (version_num >> 16) & 0xFF
var patch = (version_num >> 8) & 0xFF
return f"{major}.{minor}.{patch}"
end
animation.version_string = animation_version_string
import sys
# Takes a map returned by "import XXX" and add each key/value to module `animation`
def register_to_animation(m)
for k: m.keys()
animation.(k) = m[k]
end
end
# Import the core classes
import "core/frame_buffer" as frame_buffer
register_to_animation(frame_buffer)
import "core/pattern_base" as pattern_base
register_to_animation(pattern_base)
import "core/animation_base" as animation_base
register_to_animation(animation_base)
import "core/sequence_manager" as sequence_manager
register_to_animation(sequence_manager)
# Import the unified animation engine
import "core/animation_engine" as animation_engine
register_to_animation(animation_engine)
# Import event system
import "core/event_handler" as event_handler
register_to_animation(event_handler)
# Import user functions registry
import "core/user_functions" as user_functions
register_to_animation(user_functions)
def animation_init(m)
import global
global._event_manager = m.event_manager()
return m
end
animation.init = animation_init
# 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)
import "providers/static_value_provider.be" as static_value_provider
register_to_animation(static_value_provider)
import "providers/oscillator_value_provider.be" as oscillator_value_provider
register_to_animation(oscillator_value_provider)
# Import color providers
import "providers/color_provider.be" as color_provider
register_to_animation(color_provider)
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/rich_palette_color_provider.be" as rich_palette_color_provider
register_to_animation(rich_palette_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)
# Global variable resolver with error checking
# First checks animation module, then global scope
def animation_global(name, module_name)
import global
import introspect
# First try to find in animation module
if (module_name != nil) && introspect.contains(animation, module_name)
return animation.(module_name)
end
# Then try global scope
if global.contains(name)
return global.(name)
else
raise "syntax_error", f"'{name}' undeclared"
end
end
animation.global = animation_global
return animation

View File

@ -0,0 +1,208 @@
# 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,30 @@
/*
be_matter_module.c - implements the high level `matter` Berry module
Copyright (C) 2023 Stephan Hadinger & Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/********************************************************************
* Matter global module
*
*******************************************************************/
#ifdef USE_BERRY_ANIMATION
#include "be_constobj.h"
#include "be_mapping.h"
#include "solidify/solidified_animation.h"
#endif

View File

@ -0,0 +1,8 @@
// force include of module by including this file
#ifndef __BERRY_ANIMATION__
#define __BERRY_ANIMATION__
#endif // __BERRY_ANIMATION__

View File

@ -0,0 +1,166 @@
# 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.
class Animation : animation.pattern
# Animation-specific state variables (inherits priority, opacity, name, etc. from Pattern)
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)
# 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")
# Initialize animation-specific properties
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)
#
# @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()
self.current_time = self.start_time
return self
end
# Update animation state based on current time (override Pattern's update)
# 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
return false
end
self.current_time = time_ms
var elapsed = self.current_time - self.start_time
# Check if animation has completed its duration
if self.duration > 0 && elapsed >= self.duration
if self.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)
else
# Animation completed, stop it
self.stop()
return false
end
end
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
#
# @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)
end
# String representation of the animation
def tostring()
return f"Animation({self.name}, priority={self.priority}, duration={self.duration}, loop={self.loop}, running={self.is_running})"
end
end
return {'animation': Animation}

View File

@ -0,0 +1,392 @@
# Unified Animation Engine
# Combines AnimationController, AnimationManager, and Renderer into a single efficient class
#
# This unified approach eliminates redundancy and provides a simpler, more efficient
# animation system for Tasmota LED control.
class AnimationEngine
# Core properties
var strip # LED strip object
var width # Strip width (cached for performance)
var animations # List of active animations (sorted by priority)
var sequence_managers # List of active sequence managers
var frame_buffer # Main frame buffer
var temp_buffer # Temporary buffer for blending
# State management
var is_running # Whether engine is active
var last_update # Last update time in milliseconds
var fast_loop_closure # Stored closure for fast_loop registration
# Performance optimization
var render_needed # Whether a render pass is needed
# Initialize the animation engine for a specific LED strip
def init(strip)
if strip == nil
raise "value_error", "strip cannot be nil"
end
self.strip = strip
self.width = strip.length()
self.animations = []
self.sequence_managers = []
# Create frame buffers
self.frame_buffer = animation.frame_buffer(self.width)
self.temp_buffer = animation.frame_buffer(self.width)
# Initialize state
self.is_running = false
self.last_update = 0
self.fast_loop_closure = nil
self.render_needed = false
end
# Start the animation engine
def start()
if !self.is_running
self.is_running = true
self.last_update = tasmota.millis() - 10
if self.fast_loop_closure == nil
self.fast_loop_closure = / -> self.on_tick()
end
var i = 0
var now = tasmota.millis()
while (i < size(self.animations))
self.animations[i].start(now)
i += 1
end
tasmota.add_fast_loop(self.fast_loop_closure)
end
return self
end
# Stop the animation engine
def stop()
if self.is_running
self.is_running = false
if self.fast_loop_closure != nil
tasmota.remove_fast_loop(self.fast_loop_closure)
end
end
return self
end
# Add an animation with automatic priority sorting
def add_animation(anim)
# Check if animation already exists
var i = 0
while i < size(self.animations)
if self.animations[i] == anim
return false
end
i += 1
end
# Add and sort by priority (higher priority first)
self.animations.push(anim)
self._sort_animations()
# If the engine is already started, auto-start the animation
if self.is_running
anim.start()
end
self.render_needed = true
return true
end
# Remove an animation
def remove_animation(animation)
var index = -1
var i = 0
while i < size(self.animations)
if self.animations[i] == animation
index = i
break
end
i += 1
end
if index >= 0
self.animations.remove(index)
self.render_needed = true
return true
end
return false
end
# Clear all animations and sequences
def clear()
self.animations = []
var i = 0
while i < size(self.sequence_managers)
self.sequence_managers[i].stop_sequence()
i += 1
end
self.sequence_managers = []
self.render_needed = true
return self
end
# Add a sequence manager
def add_sequence_manager(sequence_manager)
self.sequence_managers.push(sequence_manager)
return self
end
# Remove a sequence manager
def remove_sequence_manager(sequence_manager)
var index = -1
var i = 0
while i < size(self.sequence_managers)
if self.sequence_managers[i] == sequence_manager
index = i
break
end
i += 1
end
if index >= 0
self.sequence_managers.remove(index)
return true
end
return false
end
# Main tick function called by fast_loop
def on_tick(current_time)
if !self.is_running
return false
end
if current_time == nil
current_time = tasmota.millis()
end
# Throttle updates to ~5ms intervals
var delta_time = current_time - self.last_update
if delta_time < 5
return true
end
self.last_update = current_time
# Check if strip can accept updates
if self.strip.can_show != nil && !self.strip.can_show()
return true
end
# Update sequence managers
var i = 0
while i < size(self.sequence_managers)
self.sequence_managers[i].update()
i += 1
end
# Process any queued events (non-blocking)
self._process_events(current_time)
# Update and render animations
self._update_and_render(current_time)
return true
end
# Unified update and render process
def _update_and_render(time_ms)
var active_count = 0
# First loop: update animations and remove completed ones in-line
var i = 0
while i < size(self.animations)
var anim = self.animations[i]
var still_running = anim.update(time_ms)
if still_running && anim.is_running
# Animation is still active, keep it
active_count += 1
i += 1
else
# Animation is completed, remove it in-line
self.animations.remove(i)
self.render_needed = true
# Don't increment i since we removed an element
end
end
# Skip rendering if no active animations
if active_count == 0
if self.render_needed
self._clear_strip()
self.render_needed = false
end
return
end
# Render active animations with efficient blending
self._render_animations(self.animations, time_ms)
self.render_needed = false
end
# Efficient animation rendering with minimal buffer operations
def _render_animations(animations, time_ms)
# Clear main buffer
self.frame_buffer.clear()
# Render animations in priority order (highest first)
var i = 0
while i < size(animations)
var anim = animations[i]
# Clear temp buffer and render animation
self.temp_buffer.clear()
var rendered = anim.render(self.temp_buffer, time_ms)
if rendered
# Blend temp buffer into main buffer
self.frame_buffer.blend_pixels(self.temp_buffer)
end
i += 1
end
# Output to strip
self._output_to_strip()
end
# Output frame buffer to LED strip
def _output_to_strip()
var i = 0
while i < self.width
self.strip.set_pixel_color(i, self.frame_buffer.get_pixel_color(i))
i += 1
end
self.strip.show()
end
# Clear the LED strip
def _clear_strip()
self.strip.clear()
self.strip.show()
end
# Sort animations by priority (higher first)
def _sort_animations()
var n = size(self.animations)
if n <= 1
return
end
# Insertion sort for small lists
var i = 1
while i < n
var key = self.animations[i]
var j = i
while j > 0 && self.animations[j-1].priority < key.priority
self.animations[j] = self.animations[j-1]
j -= 1
end
self.animations[j] = key
i += 1
end
end
# Event processing methods
def _process_events(current_time)
# Process any queued events from the global event manager
# This is called during fast_loop to handle events asynchronously
if global._event_manager != nil
global._event_manager._process_queued_events()
end
end
# Interrupt current animations
def interrupt_current()
# Stop all currently running animations
for anim : self.animations
if anim.is_running
anim.stop()
end
end
end
# Interrupt all animations
def interrupt_all()
self.clear()
end
# Interrupt specific animation by name
def interrupt_animation(name)
var i = 0
while i < size(self.animations)
var anim = self.animations[i]
if anim.name != nil && anim.name == name
anim.stop(anim)
self.animations.remove(i)
return
end
i += 1
end
end
# Resume animations (placeholder for future state management)
def resume()
# For now, just ensure engine is running
if !self.is_running
self.start()
end
end
# Resume after a delay (placeholder for future implementation)
def resume_after(delay_ms)
# For now, just resume immediately
# Future implementation could use a timer
self.resume()
end
# Utility methods for compatibility
def get_strip()
return self.strip
end
def is_active()
return self.is_running
end
def size()
return size(self.animations)
end
def get_animations()
return self.animations
end
# Cleanup method for proper resource management
def cleanup()
self.stop()
self.clear()
self.frame_buffer = nil
self.temp_buffer = nil
self.strip = nil
end
# String representation
def tostring()
return f"AnimationEngine(running={self.is_running}, animations={size(self.animations)}, width={self.width})"
end
end
# Main function to create the animation engine
def create_engine(strip)
return animation.animation_engine(strip)
end
# Compatibility function for legacy examples
def animation_controller(strip)
return animation.animation_engine(strip)
end
return {'animation_engine': AnimationEngine,
'create_engine': create_engine,
'animation_controller': animation_controller}

View File

@ -0,0 +1,265 @@
# Event Handler System for Berry Animation Framework
# Manages event callbacks and execution
class EventHandler
var event_name # Name of the event (e.g., "button_press", "timer")
var callback_func # Function to call when event occurs
var condition # Optional condition function (returns true/false)
var priority # Handler priority (higher = executed first)
var is_active # Whether this handler is currently active
var metadata # Additional event metadata (e.g., timer interval)
def init(event_name, callback_func, priority, condition, metadata)
self.event_name = event_name
self.callback_func = callback_func
self.priority = priority != nil ? priority : 0
self.condition = condition
self.is_active = true
self.metadata = metadata != nil ? metadata : {}
end
# Execute the event handler if conditions are met
def execute(event_data)
if !self.is_active
return false
end
# Check condition if provided
if self.condition != nil
if !self.condition(event_data)
return false
end
end
# Execute callback
if self.callback_func != nil
self.callback_func(event_data)
return true
end
return false
end
# Enable/disable the handler
def set_active(active)
self.is_active = active
end
# Get handler info for debugging
def get_info()
return {
"event_name": self.event_name,
"priority": self.priority,
"is_active": self.is_active,
"has_condition": self.condition != nil,
"metadata": self.metadata
}
end
end
#@ solidify:EventManager,weak
class EventManager
var handlers # Map of event_name -> list of handlers
var global_handlers # Handlers that respond to all events
var event_queue # Simple event queue for deferred processing
var is_processing # Flag to prevent recursive event processing
def init()
self.handlers = {}
self.global_handlers = []
self.event_queue = []
self.is_processing = false
end
# Register an event handler
def register_handler(event_name, callback_func, priority, condition, metadata)
var handler = animation.event_handler(event_name, callback_func, priority, condition, metadata)
if event_name == "*"
# Global handler for all events
self.global_handlers.push(handler)
self._sort_handlers(self.global_handlers)
else
# Specific event handler
if !self.handlers.contains(event_name)
self.handlers[event_name] = []
end
self.handlers[event_name].push(handler)
self._sort_handlers(self.handlers[event_name])
end
return handler
end
# Remove an event handler
def unregister_handler(handler)
if handler.event_name == "*"
var idx = self.global_handlers.find(handler)
if idx != nil
self.global_handlers.remove(idx)
end
else
var event_handlers = self.handlers.find(handler.event_name)
if event_handlers != nil
var idx = event_handlers.find(handler)
if idx != nil
event_handlers.remove(idx)
end
end
end
end
# Trigger an event immediately
def trigger_event(event_name, event_data)
if self.is_processing
# Queue event to prevent recursion
self.event_queue.push({"name": event_name, "data": event_data})
return
end
self.is_processing = true
try
# Execute global handlers first
for handler : self.global_handlers
if handler.is_active
handler.execute({"event_name": event_name, "data": event_data})
end
end
# Execute specific event handlers
var event_handlers = self.handlers.find(event_name)
if event_handlers != nil
for handler : event_handlers
if handler.is_active
handler.execute(event_data)
end
end
end
except .. as e, msg
print("Event processing error:", e, msg)
end
self.is_processing = false
# Process queued events
self._process_queued_events()
end
# Process any queued events
def _process_queued_events()
while self.event_queue.size() > 0
var queued_event = self.event_queue.pop(0)
self.trigger_event(queued_event["name"], queued_event["data"])
end
end
# Sort handlers by priority (higher priority first)
def _sort_handlers(handler_list)
# Insertion sort for small lists (embedded-friendly and efficient)
for i : 1..size(handler_list)-1
var k = handler_list[i]
var j = i
while (j > 0) && (handler_list[j-1].priority < k.priority)
handler_list[j] = handler_list[j-1]
j -= 1
end
handler_list[j] = k
end
end
# Get all registered events
def get_registered_events()
var events = []
for event_name : self.handlers.keys()
events.push(event_name)
end
return events
end
# Get handlers for a specific event
def get_handlers(event_name)
var result = []
# Add global handlers
for handler : self.global_handlers
result.push(handler.get_info())
end
# Add specific handlers
var event_handlers = self.handlers.find(event_name)
if event_handlers != nil
for handler : event_handlers
result.push(handler.get_info())
end
end
return result
end
# Clear all handlers
def clear_all_handlers()
self.handlers.clear()
self.global_handlers.clear()
self.event_queue.clear()
end
# Enable/disable all handlers for an event
def set_event_active(event_name, active)
var event_handlers = self.handlers.find(event_name)
if event_handlers != nil
for handler : event_handlers
handler.set_active(active)
end
end
end
end
# Event system functions to monad
def register_event_handler(event_name, callback_func, priority, condition, metadata)
import global
return global._event_manager.register_handler(event_name, callback_func, priority, condition, metadata)
end
def unregister_event_handler(handler)
import global
global._event_manager.unregister_handler(handler)
end
def trigger_event(event_name, event_data)
import global
global._event_manager.trigger_event(event_name, event_data)
end
def get_registered_events()
return global._event_manager.get_registered_events()
end
def get_event_handlers(event_name)
import global
return global._event_manager.get_handlers(event_name)
end
def clear_all_event_handlers()
import global
global._event_manager.clear_all_handlers()
end
def set_event_active(event_name, active)
import global
global._event_manager.set_event_active(event_name, active)
end
# Export classes
return {
"event_handler": EventHandler,
"event_manager": EventManager,
'register_event_handler': register_event_handler,
'unregister_event_handler': unregister_event_handler,
'trigger_event': trigger_event,
'get_registered_events': get_registered_events,
'get_event_handlers': get_event_handlers,
'clear_all_event_handlers': clear_all_event_handlers,
'set_event_active': set_event_active,
}

View File

@ -0,0 +1,584 @@
# FrameBuffer class for Berry Animation Framework
#
# This class provides a buffer for storing and manipulating pixel data
# for LED animations. It uses a bytes object for efficient storage and
# provides methods for pixel manipulation.
#
# Each pixel is stored as a 32-bit value (ARGB format - 0xAARRGGBB):
# - 8 bits for Alpha (0-255, where 0 is fully transparent and 255 is fully opaque)
# - 8 bits for Red (0-255)
# - 8 bits for Green (0-255)
# - 8 bits for Blue (0-255)
#
# The class is optimized for performance and minimal memory usage.
class FrameBuffer
# Blend modes
# Currently only normal blending is implemented, but the structure allows for adding more modes later
static BLEND_MODE_NORMAL = 0 # Normal alpha blending
# Other blend modes can be added here in the future if needed
var pixels # Pixel data (bytes object)
var width # Number of pixels
# Initialize a new frame buffer with the specified width
# Takes either an int (width) or an instance of FrameBuffer (instance)
def init(width_or_buffer)
if type(width_or_buffer) == 'int'
var width = width_or_buffer
if width <= 0
raise "value_error", "width must be positive"
end
self.width = width
# Each pixel uses 4 bytes (ARGB), so allocate width * 4 bytes
# Initialize with zeros to ensure correct size
var buffer = bytes(width * 4)
buffer.resize(width * 4)
self.pixels = buffer
self.clear() # Initialize all pixels to transparent black
elif type(width_or_buffer) == 'instance'
self.width = width_or_buffer.width
self.pixels = width_or_buffer.pixels.copy()
else
raise "value_error", "argument must be either int or instance"
end
end
# Get the pixel color at the specified index
# Returns the pixel value as a 32-bit integer (ARGB format - 0xAARRGGBB)
def get_pixel_color(index)
if index < 0 || index >= self.width
raise "index_error", "pixel index out of range"
end
# Each pixel is 4 bytes, so the offset is index * 4
return self.pixels.get(index * 4, 4)
end
# Set the pixel at the specified index with a 32-bit color value
# color: 32-bit color value in ARGB format (0xAARRGGBB)
def set_pixel_color(index, color)
if index < 0 || index >= self.width
raise "index_error", "pixel index out of range"
end
# Set the pixel in the buffer
self.pixels.set(index * 4, color, 4)
end
# Clear the frame buffer (set all pixels to transparent black)
def clear()
self.pixels.clear() # clear buffer
self.pixels.resize(self.width * 4) # resize to full size filled with transparent black (all zeroes)
end
# Convert separate a, r, g, b components to a 32-bit color value
# r: red component (0-255)
# g: green component (0-255)
# b: blue component (0-255)
# a: alpha component (0-255, default 255 = fully opaque)
# Returns: 32-bit color value in ARGB format (0xAARRGGBB)
static def to_color(r, g, b, a)
# Default alpha to fully opaque if not specified
if a == nil
a = 255
end
# Ensure values are in valid range
r = r & 0xFF
g = g & 0xFF
b = b & 0xFF
a = a & 0xFF
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
return (a << 24) | (r << 16) | (g << 8) | b
end
# Fill the frame buffer with a specific color using a bytes object
# This is an optimization for filling with a pre-computed color
def fill_pixels(color)
var i = 0
while i < self.width
self.pixels.set(i * 4, color, 4)
i += 1
end
end
# Blend two colors using their alpha channels and blend mode
# Returns the blended color as a 32-bit integer (ARGB format - 0xAARRGGBB)
# color1: destination color (ARGB format - 0xAARRGGBB)
# color2: source color (ARGB format - 0xAARRGGBB)
# mode: blending mode (default: BLEND_MODE_NORMAL)
def blend(color1, color2, mode)
# Default blend mode to normal if not specified
if mode == nil
mode = self.BLEND_MODE_NORMAL
end
# Extract components from color1 (ARGB format - 0xAARRGGBB)
var a1 = (color1 >> 24) & 0xFF
var r1 = (color1 >> 16) & 0xFF
var g1 = (color1 >> 8) & 0xFF
var b1 = color1 & 0xFF
# Extract components from color2 (ARGB format - 0xAARRGGBB)
var a2 = (color2 >> 24) & 0xFF
var r2 = (color2 >> 16) & 0xFF
var g2 = (color2 >> 8) & 0xFF
var b2 = color2 & 0xFF
# Fast path for common cases
if a2 == 0
# Source is fully transparent, no blending needed
return color1
end
# Use the source alpha directly for blending
var effective_opacity = a2
# Normal alpha blending (currently the only supported mode)
# Use tasmota.scale_uint for ratio conversion instead of integer arithmetic
var r = tasmota.scale_uint(255 - effective_opacity, 0, 255, 0, r1) + tasmota.scale_uint(effective_opacity, 0, 255, 0, r2)
var g = tasmota.scale_uint(255 - effective_opacity, 0, 255, 0, g1) + tasmota.scale_uint(effective_opacity, 0, 255, 0, g2)
var b = tasmota.scale_uint(255 - effective_opacity, 0, 255, 0, b1) + tasmota.scale_uint(effective_opacity, 0, 255, 0, b2)
# More accurate alpha blending using tasmota.scale_uint
var a = a1 + tasmota.scale_uint((255 - a1) * a2, 0, 255 * 255, 0, 255)
# Ensure values are in valid range
r = r < 0 ? 0 : (r > 255 ? 255 : r)
g = g < 0 ? 0 : (g > 255 ? 255 : g)
b = b < 0 ? 0 : (b > 255 ? 255 : b)
a = a < 0 ? 0 : (a > 255 ? 255 : a)
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
return (int(a) << 24) | (int(r) << 16) | (int(g) << 8) | int(b)
end
# Blend this frame buffer with another frame buffer using per-pixel alpha
# other_buffer: the other frame buffer to blend with
# mode: blending mode (default: BLEND_MODE_NORMAL)
# region_start: start index for blending (default: 0)
# region_end: end index for blending (default: width-1)
def blend_pixels(other_buffer, mode, region_start, region_end)
# Default parameters
if mode == nil
mode = self.BLEND_MODE_NORMAL
end
if region_start == nil
region_start = 0
end
if region_end == nil
region_end = self.width - 1
end
# Validate parameters
if self.width != other_buffer.width
raise "value_error", "frame buffers must have the same width"
end
if region_start < 0 || region_start >= self.width
raise "index_error", "region_start out of range"
end
if region_end < region_start || region_end >= self.width
raise "index_error", "region_end out of range"
end
# Performance optimization: batch processing for normal blend mode
if mode == self.BLEND_MODE_NORMAL
# Fast path for normal blending
var i = region_start
while i <= region_end
var color2 = other_buffer.get_pixel_color(i)
var a2 = (color2 >> 24) & 0xFF
# Only blend if the source pixel has some alpha
if a2 > 0
if a2 == 255
# Fully opaque source pixel, just copy it
self.pixels.set(i * 4, color2, 4)
else
# Partially transparent source pixel, need to blend
var color1 = self.get_pixel_color(i)
var blended = self.blend(color1, color2, mode)
self.pixels.set(i * 4, blended, 4)
end
end
i += 1
end
return
end
# General case: blend each pixel using the blend function
var i = region_start
while i <= region_end
var color1 = self.get_pixel_color(i)
var color2 = other_buffer.get_pixel_color(i)
# Only blend if the source pixel has some alpha
var a2 = (color2 >> 24) & 0xFF
if a2 > 0
var blended = self.blend(color1, color2, mode)
self.pixels.set(i * 4, blended, 4)
end
i += 1
end
end
# Convert the frame buffer to a hexadecimal string (for debugging)
def tohex()
return self.pixels.tohex()
end
# Support for array-like access using []
def item(i)
return self.get_pixel_color(i)
end
# Support for array-like assignment using []=
def setitem(i, v)
# Use the set_pixel_color method directly with the 32-bit value
self.set_pixel_color(i, v)
end
# Create a gradient fill in the frame buffer
# color1: start color (ARGB format - 0xAARRGGBB)
# color2: end color (ARGB format - 0xAARRGGBB)
# start_pos: start position (default: 0)
# end_pos: end position (default: width-1)
def gradient_fill(color1, color2, start_pos, end_pos)
if start_pos == nil
start_pos = 0
end
if end_pos == nil
end_pos = self.width - 1
end
# Validate parameters
if start_pos < 0 || start_pos >= self.width
raise "index_error", "start_pos out of range"
end
if end_pos < start_pos || end_pos >= self.width
raise "index_error", "end_pos out of range"
end
# Set first pixel directly
self.set_pixel_color(start_pos, color1)
# If only one pixel, we're done
if start_pos == end_pos
return
end
# Set last pixel directly
self.set_pixel_color(end_pos, color2)
# If only two pixels, we're done
if end_pos - start_pos <= 1
return
end
# Extract components from color1 (ARGB format - 0xAARRGGBB)
var a1 = (color1 >> 24) & 0xFF
var r1 = (color1 >> 16) & 0xFF
var g1 = (color1 >> 8) & 0xFF
var b1 = color1 & 0xFF
# Extract components from color2 (ARGB format - 0xAARRGGBB)
var a2 = (color2 >> 24) & 0xFF
var r2 = (color2 >> 16) & 0xFF
var g2 = (color2 >> 8) & 0xFF
var b2 = color2 & 0xFF
# Calculate the total number of steps
var steps = end_pos - start_pos
# Fill the gradient for intermediate pixels
var i = start_pos + 1
while (i < end_pos)
var pos = i - start_pos
# Use tasmota.scale_uint for ratio conversion instead of floating point arithmetic
var r = tasmota.scale_uint(pos, 0, steps, r1, r2)
var g = tasmota.scale_uint(pos, 0, steps, g1, g2)
var b = tasmota.scale_uint(pos, 0, steps, b1, b2)
var a = tasmota.scale_uint(pos, 0, steps, a1, a2)
# Ensure values are in valid range
r = r < 0 ? 0 : (r > 255 ? 255 : r)
g = g < 0 ? 0 : (g > 255 ? 255 : g)
b = b < 0 ? 0 : (b > 255 ? 255 : b)
a = a < 0 ? 0 : (a > 255 ? 255 : a)
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
var color = (a << 24) | (r << 16) | (g << 8) | b
self.set_pixel_color(i, color)
i += 1
end
end
# Apply a mask to this frame buffer
# mask_buffer: the mask frame buffer (alpha channel is used as mask)
# invert: if true, invert the mask (default: false)
def apply_mask(mask_buffer, invert)
if invert == nil
invert = false
end
if self.width != mask_buffer.width
raise "value_error", "frame buffers must have the same width"
end
var i = 0
while i < self.width
var color = self.get_pixel_color(i)
var mask_color = mask_buffer.get_pixel_color(i)
# Extract alpha from mask (0-255)
var mask_alpha = (mask_color >> 24) & 0xFF
# Invert mask if requested
if invert
mask_alpha = 255 - mask_alpha
end
# Extract components from color (ARGB format - 0xAARRGGBB)
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
# Apply mask to alpha channel using tasmota.scale_uint
a = tasmota.scale_uint(mask_alpha, 0, 255, 0, a)
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
var new_color = (a << 24) | (r << 16) | (g << 8) | b
# Update the pixel
self.set_pixel_color(i, new_color)
i += 1
end
end
# Create a copy of this frame buffer
def copy()
return animation.frame_buffer(self) # return using the self copying constructor
end
# Blend a specific region with a solid color using the color's alpha channel
# color: the color to blend (ARGB)
# mode: blending mode (default: BLEND_MODE_NORMAL)
# start_pos: start position (default: 0)
# end_pos: end position (default: width-1)
def blend_color(color, mode, start_pos, end_pos)
if mode == nil
mode = self.BLEND_MODE_NORMAL
end
if start_pos == nil
start_pos = 0
end
if end_pos == nil
end_pos = self.width - 1
end
# Validate parameters
if start_pos < 0 || start_pos >= self.width
raise "index_error", "start_pos out of range"
end
if end_pos < start_pos || end_pos >= self.width
raise "index_error", "end_pos out of range"
end
# Extract components from color (ARGB format - 0xAARRGGBB)
var a2 = (color >> 24) & 0xFF
var r2 = (color >> 16) & 0xFF
var g2 = (color >> 8) & 0xFF
var b2 = color & 0xFF
# Blend the pixels in the specified region
var i = start_pos
while i <= end_pos
var color1 = self.get_pixel_color(i)
# Only blend if the color has some alpha
if a2 > 0
var blended = self.blend(color1, color, mode)
self.pixels.set(i * 4, blended, 4)
end
i += 1
end
end
# Apply an opacity adjustment to the frame buffer
# opacity: opacity factor (0-511, where 0 is fully transparent, 255 is original, 511 is maximum opaque)
# start_pos: start position (default: 0)
# end_pos: end position (default: width-1)
def apply_opacity(opacity, start_pos, end_pos)
if opacity == nil
opacity = 255
end
if start_pos == nil
start_pos = 0
end
if end_pos == nil
end_pos = self.width - 1
end
# Validate parameters
if start_pos < 0 || start_pos >= self.width
raise "index_error", "start_pos out of range"
end
if end_pos < start_pos || end_pos >= self.width
raise "index_error", "end_pos out of range"
end
# Ensure opacity is in valid range (0-511)
opacity = opacity < 0 ? 0 : (opacity > 511 ? 511 : opacity)
# Apply opacity adjustment
var i = start_pos
while i <= end_pos
var color = self.get_pixel_color(i)
# Extract components (ARGB format - 0xAARRGGBB)
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
# Adjust alpha using tasmota.scale_uint
# For opacity 0-255: scale down alpha
# For opacity 256-511: scale up alpha (but cap at 255)
if opacity <= 255
a = tasmota.scale_uint(opacity, 0, 255, 0, a)
else
# Scale up alpha: map 256-511 to 1.0-2.0 multiplier
a = tasmota.scale_uint(a * opacity, 0, 255 * 255, 0, 255)
a = a > 255 ? 255 : a # Cap at maximum alpha
end
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
color = (a << 24) | (r << 16) | (g << 8) | b
# Update the pixel
self.set_pixel_color(i, color)
i += 1
end
end
# Apply a brightness adjustment to the frame buffer
# brightness: brightness factor (0-511, where 0 is black, 255 is original, and 511 is maximum bright)
# start_pos: start position (default: 0)
# end_pos: end position (default: width-1)
def apply_brightness(brightness, start_pos, end_pos)
if brightness == nil
brightness = 255
end
if start_pos == nil
start_pos = 0
end
if end_pos == nil
end_pos = self.width - 1
end
# Validate parameters
if start_pos < 0 || start_pos >= self.width
raise "index_error", "start_pos out of range"
end
if end_pos < start_pos || end_pos >= self.width
raise "index_error", "end_pos out of range"
end
# Ensure brightness is in valid range (0-511)
brightness = brightness < 0 ? 0 : (brightness > 511 ? 511 : brightness)
# Apply brightness adjustment
var i = start_pos
while i <= end_pos
var color = self.get_pixel_color(i)
# Extract components (ARGB format - 0xAARRGGBB)
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
# Adjust brightness using tasmota.scale_uint
# For brightness 0-255: scale down RGB
# For brightness 256-511: scale up RGB (but cap at 255)
if brightness <= 255
r = tasmota.scale_uint(r, 0, 255, 0, brightness)
g = tasmota.scale_uint(g, 0, 255, 0, brightness)
b = tasmota.scale_uint(b, 0, 255, 0, brightness)
else
# Scale up RGB: map 256-511 to 1.0-2.0 multiplier
var multiplier = brightness - 255 # 0-256 range
r = r + tasmota.scale_uint(r * multiplier, 0, 255 * 256, 0, 255)
g = g + tasmota.scale_uint(g * multiplier, 0, 255 * 256, 0, 255)
b = b + tasmota.scale_uint(b * multiplier, 0, 255 * 256, 0, 255)
r = r > 255 ? 255 : r # Cap at maximum
g = g > 255 ? 255 : g # Cap at maximum
b = b > 255 ? 255 : b # Cap at maximum
end
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
color = (a << 24) | (r << 16) | (g << 8) | b
# Update the pixel
self.set_pixel_color(i, color)
i += 1
end
end
# String representation of the frame buffer
def tostring()
return f"FrameBuffer(width={self.width}, pixels={self.pixels})"
end
# Dump the pixels into AARRGGBB string separated with '|'
def dump()
var s = ""
var i = 0
while i < self.width
var color = self.get_pixel_color(i)
# Extract components from color (ARGB format - 0xAARRGGBB)
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
s += f"{a:02X}{r:02X}{g:02X}{b:02X}|"
i += 1
end
s = s[0..-2] # remove last character
return s
end
end
return {'frame_buffer': FrameBuffer}

View File

@ -0,0 +1,381 @@
# 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

@ -0,0 +1,165 @@
# Sequence Manager for Animation DSL
# Handles async execution of animation sequences without blocking delays
class SequenceManager
var controller # Animation controller reference
var active_sequence # Currently running sequence
var sequence_state # Current sequence execution state
var step_index # Current step in the sequence
var step_start_time # When current step started
var steps # List of sequence steps
var is_running # Whether sequence is active
def init(controller)
self.controller = controller
self.active_sequence = nil
self.sequence_state = {}
self.step_index = 0
self.step_start_time = 0
self.steps = []
self.is_running = false
end
# Start a new sequence
def start_sequence(steps)
self.stop_sequence() # Stop any current sequence
self.steps = steps
self.step_index = 0
self.step_start_time = tasmota.millis()
self.is_running = true
if size(self.steps) > 0
self.execute_current_step()
end
end
# Stop the current sequence
def stop_sequence()
if self.is_running
self.is_running = false
self.controller.clear()
end
end
# Update sequence state - called from fast_loop
def update()
if !self.is_running || size(self.steps) == 0
return
end
var current_time = tasmota.millis()
var current_step = self.steps[self.step_index]
# Check if current step has completed
if current_step.contains("duration") && current_step["duration"] > 0
var elapsed = current_time - self.step_start_time
if elapsed >= current_step["duration"]
self.advance_to_next_step()
end
else
# Steps without duration (like stop steps) complete immediately
# Advance to next step on next update cycle
self.advance_to_next_step()
end
end
# Execute the current step
def execute_current_step()
if self.step_index >= size(self.steps)
self.is_running = false
return
end
var step = self.steps[self.step_index]
if step["type"] == "play"
var anim = step["animation"]
self.controller.add_animation(anim)
anim.start()
# Set duration if specified
if step.contains("duration") && step["duration"] > 0
anim.set_duration(step["duration"])
end
elif step["type"] == "wait"
# Wait steps are handled by the update loop checking duration
# No animation needed for wait
elif step["type"] == "stop"
var anim = step["animation"]
anim.stop()
self.controller.remove_animation(anim)
end
self.step_start_time = tasmota.millis()
end
# Advance to the next step in the sequence
def advance_to_next_step()
# Stop current animations if step had duration
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
self.step_index += 1
if self.step_index >= size(self.steps)
self.is_running = false
return
end
self.execute_current_step()
end
# Check if sequence is running
def is_sequence_running()
return self.is_running
end
# Get current step info for debugging
def get_current_step_info()
if !self.is_running || self.step_index >= size(self.steps)
return nil
end
return {
"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
}
end
end
# Helper function to create sequence steps
def create_play_step(animation, duration)
return {
"type": "play",
"animation": animation,
"duration": duration
}
end
def create_wait_step(duration)
return {
"type": "wait",
"duration": duration
}
end
def create_stop_step(animation)
return {
"type": "stop",
"animation": animation
}
end
return {'SequenceManager': SequenceManager,
'create_play_step': create_play_step,
'create_wait_step': create_wait_step,
'create_stop_step': create_stop_step}

View File

@ -0,0 +1,44 @@
# User-Defined Functions Registry for Berry Animation Framework
# This module manages external Berry functions that can be called from DSL code
#@ solidify:animation_user_functions,weak
# Module-level storage for user-defined functions
import global
global._animation_user_functions = {}
# Register a Berry function for DSL use
def register_user_function(name, func)
import global
global._animation_user_functions[name] = func
end
# Retrieve a registered function by name
def get_user_function(name)
import global
return global._animation_user_functions.find(name)
end
# Check if a function is registered
def is_user_function(name)
import global
return global._animation_user_functions.contains(name)
end
# List all registered function names
def list_user_functions()
import global
var names = []
for name : global._animation_user_functions.keys()
names.push(name)
end
return names
end
# Export all functions
return {
"register_user_function": register_user_function,
"get_user_function": get_user_function,
"is_user_function": is_user_function,
"list_user_functions": list_user_functions
}

View File

@ -0,0 +1,750 @@
# 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,568 @@
# Examples
Curated examples showcasing the Tasmota Berry Animation Framework capabilities.
## Basic Examples
### 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:**
```dsl
color red = #FF0000
animation solid_red = solid(red)
run solid_red
```
### 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 Version:**
```dsl
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 3s, 20%, 100%)
run pulse_blue
```
### 3. Breathing Effect
**DSL:**
```dsl
color soft_white = #C0C0C0
animation breathing = breathe(soft_white, 4s)
run breathing
```
## Color and Palette Examples
### 4. Fire Effect
**DSL:**
```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)
run rainbow_cycle
```
### 6. Ocean Waves
**DSL:**
```dsl
palette ocean = [
(0, navy), # Deep ocean
(64, blue), # Ocean blue
(128, cyan), # Shallow water
(192, #87CEEB), # Sky blue
(255, white) # Foam
]
animation ocean_waves = rich_palette_animation(ocean, 6s, smooth, 200)
run ocean_waves
```
## Position-Based Examples
### 7. Center Pulse
**DSL:**
```dsl
strip length 60
color white = #FFFFFF
# Pulse at center position
animation center_pulse = pulse_position_animation(white, 30, 5, 3)
run center_pulse
```
### 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
}
run rgb_show
```
### 14. Sunrise Sequence
**DSL:**
```dsl
# Define sunrise colors
color deep_blue = #000080
color purple = #800080
color pink = #FF69B4
color orange = #FFA500
color yellow = #FFFF00
# 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)
# 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
}
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:**
```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
]
# 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
```
### 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:**
```dsl
# Define single palette for multiple uses
palette shared_rainbow = [
(0, red), (51, orange), (102, yellow),
(153, green), (204, blue), (255, violet)
]
# 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)
# Use in sequence to avoid simultaneous memory usage
sequence efficient_show {
play fast_rainbow for 15s
wait 1s
play slow_rainbow for 20s
}
run efficient_show
```
## Tips for Creating Your Own Examples
### 1. Start Simple
Begin with basic solid colors and simple pulses before adding complexity.
### 2. Use Meaningful Names
```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%)
```
### 3. Comment Your Code
```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
]
```
### 4. Test Incrementally
Build complex animations step by step:
1. Test basic colors
2. Add simple effects
3. Combine with sequences
4. Add interactivity
### 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
## 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
Experiment with these examples and create your own amazing LED animations! 🎨✨

View File

@ -0,0 +1,231 @@
# 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)
├── 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 (`compiled/`)
Berry code generated from DSL examples (for reference):
```
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 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

@ -0,0 +1,253 @@
# Quick Start Guide
Get up and running with the Tasmota Berry Animation Framework in 5 minutes!
## Prerequisites
- Tasmota device with Berry support
- Addressable LED strip (WS2812, SK6812, etc.)
- Basic familiarity with Tasmota console
## Step 1: Basic Setup
### Import the Framework
```berry
import animation
```
### 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%)
# Run it
run pulse_red
```
### Load DSL Animation
```berry
import animation
var strip = Leds(30)
var runtime = animation.DSLRuntime(animation.create_engine(strip))
# Load from string
var dsl_code = '''
color blue = #0000FF
animation pulse_blue = pulse(solid(blue), 2s, 30%, 100%)
run pulse_blue
'''
runtime.load_dsl(dsl_code)
```
## Step 4: Color Palettes
Palettes create smooth color transitions:
```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 palette animation
animation sunset_glow = rich_palette_animation(sunset, 5s, smooth, 200)
run sunset_glow
```
## Step 5: 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%)
sequence rgb_show {
play red_pulse for 3s
wait 500ms
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
play blue_pulse for 1s
}
run rgb_show
```
## Step 6: Interactive Animations
Add event handling for interactive effects:
```dsl
color white = #FFFFFF
color red = #FF0000
animation flash_white = solid(white)
animation normal_red = solid(red)
# Flash white when button pressed
on button_press: flash_white
# Main animation
run normal_red
```
## 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_effect = rich_palette_animation(fire, 2s, smooth, 255)
run fire_effect
```
### Rainbow Cycle
```dsl
palette rainbow = [
(0, red), (42, orange), (84, yellow),
(126, green), (168, blue), (210, indigo), (255, violet)
]
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
```
## Tips for Success
### 1. Start Simple
Begin with solid colors and basic pulses before moving to complex effects.
### 2. Use the DSL
The DSL is much easier than writing Berry code directly.
### 3. Test Incrementally
Add one animation at a time and test before adding complexity.
### 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()
# Check if animation was added
print(engine.size()) # Should be > 0
```
### 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
```
### 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)
end
```
### 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
```
## 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
Happy animating! 🎨✨

View File

@ -0,0 +1,599 @@
# Troubleshooting Guide
Common issues and solutions for the Tasmota Berry Animation Framework.
## Installation Issues
### Framework Not Found
**Problem:** `import animation` fails with "module not found"
**Solutions:**
1. **Check Module Path:**
```berry
# Verify the animation module exists
import sys
print(sys.path())
```
2. **Set Module Path:**
```bash
berry -m lib/libesp32/berry_animation
```
3. **Verify File Structure:**
```
lib/libesp32/berry_animation/
├── animation.be # Main module file
├── core/ # Core classes
├── effects/ # Animation effects
└── ...
```
### Missing Dependencies
**Problem:** Errors about missing `tasmota` or `Leds` classes
**Solutions:**
1. **For Tasmota Environment:**
- Ensure you're running on actual Tasmota firmware
- Check that Berry support is enabled
2. **For Development Environment:**
```berry
# Mock Tasmota for testing
if !global.contains("tasmota")
global.tasmota = {
"millis": def() return 1000 end,
"scale_uint": def(val, from_min, from_max, to_min, to_max)
return int((val - from_min) * (to_max - to_min) / (from_max - from_min) + to_min)
end
}
end
```
## Animation Issues
### Animations Not Starting
**Problem:** Animations created but LEDs don't change
**Diagnostic Steps:**
```berry
import animation
var strip = Leds(30)
var engine = animation.create_engine(strip)
var anim = animation.solid(0xFFFF0000)
# 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())
```
**Common Solutions:**
1. **Forgot to Start Engine:**
```berry
engine.add_animation(anim)
engine.start() # Don't forget this!
```
2. **Animation Not Added:**
```berry
# Make sure animation is added to engine
engine.add_animation(anim)
print("Animation count:", engine.size()) # Should be > 0
```
3. **Strip Not Configured:**
```berry
# Check strip configuration
var strip = Leds(30) # 30 LEDs
print("Strip created:", strip != nil)
```
### Colors Look Wrong
**Problem:** Colors appear different than expected
**Common Issues:**
1. **Missing Alpha Channel:**
```berry
# Wrong - missing alpha
var red = 0xFF0000
# Correct - with alpha channel
var red = 0xFFFF0000 # ARGB format
```
2. **Color Format Confusion:**
```berry
# 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
```
3. **Brightness Issues:**
```berry
# Check opacity settings
anim.set_opacity(255) # Full brightness
# Check pulse brightness ranges
var pulse = animation.pulse(pattern, 2000, 50, 255) # Min=50, Max=255
```
### Animations Too Fast/Slow
**Problem:** Animation timing doesn't match expectations
**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
```
2. **Adjust Periods:**
```berry
# Too fast - increase period
var slow_pulse = animation.pulse(pattern, 5000, 50, 255) # 5 seconds
# Too slow - decrease period
var fast_pulse = animation.pulse(pattern, 500, 50, 255) # 0.5 seconds
```
3. **Performance Limitations:**
```berry
# Reduce number of simultaneous animations
engine.clear() # Remove all animations
engine.add_animation(single_animation)
```
## DSL Issues
### DSL Compilation Errors
**Problem:** DSL code fails to compile
**Diagnostic Approach:**
```berry
try
var berry_code = animation.compile_dsl(dsl_source)
print("Compilation successful")
except "dsl_compilation_error" as e, msg
print("DSL Error:", msg)
end
```
**Common DSL Errors:**
1. **Undefined Colors:**
```dsl
# Wrong - color not defined
animation red_anim = solid(red)
# Correct - define color first
color red = #FF0000
animation red_anim = solid(red)
```
2. **Invalid Color Format:**
```dsl
# Wrong - invalid hex format
color red = FF0000
# Correct - with # prefix
color red = #FF0000
```
3. **Missing Time Units:**
```dsl
# Wrong - no time unit
animation pulse_anim = pulse(solid(red), 2000, 50%, 100%)
# Correct - with time unit
animation pulse_anim = pulse(solid(red), 2s, 50%, 100%)
```
4. **Reserved Name Conflicts:**
```dsl
# Wrong - 'red' is a predefined color
color red = #800000
# Correct - use different name
color dark_red = #800000
```
### DSL Runtime Errors
**Problem:** DSL compiles but fails at runtime
**Common Issues:**
1. **Strip Not Initialized:**
```dsl
# Add strip declaration if needed
strip length 30
color red = #FF0000
animation red_anim = solid(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
sequence demo {
play red_anim for 3s # Use after definition
}
```
## Performance Issues
### Choppy Animations
**Problem:** Animations appear jerky or stuttering
**Solutions:**
1. **Reduce Animation Count:**
```berry
# Good - 1-3 animations
engine.clear()
engine.add_animation(main_animation)
# Avoid - too many simultaneous animations
# engine.add_animation(anim1)
# engine.add_animation(anim2)
# ... (10+ animations)
```
2. **Increase Animation Periods:**
```berry
# Smooth - longer periods
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
# Choppy - very short periods
var choppy_pulse = animation.pulse(pattern, 50, 50, 255) # 50ms
```
3. **Optimize Value Providers:**
```berry
# Efficient - reuse providers
var breathing = animation.smooth(50, 255, 2000)
var anim1 = animation.pulse(pattern1, breathing)
var anim2 = animation.pulse(pattern2, breathing) # Reuse
# Inefficient - create new providers
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000))
```
### Memory Issues
**Problem:** Out of memory errors or system crashes
**Solutions:**
1. **Clear Unused Animations:**
```berry
# Clear before adding new animations
engine.clear()
engine.add_animation(new_animation)
```
2. **Limit Palette Size:**
```dsl
# Good - reasonable palette size
palette simple_fire = [
(0, #000000),
(128, #FF0000),
(255, #FFFF00)
]
# Avoid - very large palettes
# palette huge_palette = [
# (0, color1), (1, color2), ... (255, color256)
# ]
```
3. **Use Sequences Instead of Simultaneous Animations:**
```dsl
# Memory efficient - sequential playback
sequence show {
play animation1 for 5s
play animation2 for 5s
play animation3 for 5s
}
# Memory intensive - all at once
# run animation1
# run animation2
# run animation3
```
## Event System Issues
### Events Not Triggering
**Problem:** Event handlers don't execute
**Diagnostic Steps:**
```berry
# Check if handler is registered
var handlers = animation.get_event_handlers("button_press")
print("Handler count:", size(handlers))
# Test event triggering
animation.trigger_event("test_event", {"debug": true})
```
**Solutions:**
1. **Verify Handler Registration:**
```berry
def test_handler(event_data)
print("Event triggered:", event_data)
end
var handler = animation.register_event_handler("test", test_handler, 0)
print("Handler registered:", handler != nil)
```
2. **Check Event Names:**
```berry
# Event names are case-sensitive
animation.register_event_handler("button_press", handler) # Correct
animation.trigger_event("button_press", {}) # Must match exactly
```
3. **Verify Conditions:**
```berry
def condition_func(event_data)
return event_data.contains("required_field")
end
animation.register_event_handler("event", handler, 0, condition_func)
# Event data must satisfy condition
animation.trigger_event("event", {"required_field": "value"})
```
## Hardware Issues
### LEDs Not Responding
**Problem:** Framework runs but LEDs don't light up
**Hardware Checks:**
1. **Power Supply:**
- Ensure adequate power for LED count
- Check voltage (5V for WS2812)
- Verify ground connections
2. **Wiring:**
- Data line connected to correct GPIO
- Ground connected between controller and LEDs
- Check for loose connections
3. **LED Strip:**
- Test with known working code
- Check for damaged LEDs
- Verify strip type (WS2812, SK6812, etc.)
**Software Checks:**
```berry
# Test basic LED functionality
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
```
### Wrong Colors on Hardware
**Problem:** Colors look different on actual LEDs vs. expected
**Solutions:**
1. **Color Order:**
```berry
# Some strips use different color orders
# Try different strip types in Tasmota configuration
# WS2812: RGB order
# SK6812: GRBW order
```
2. **Gamma Correction:**
```berry
# Enable gamma correction in Tasmota
# SetOption37 128 # Enable gamma correction
```
3. **Power Supply Issues:**
- Voltage drop causes color shifts
- Use adequate power supply
- Add power injection for long strips
## Debugging Techniques
### Enable Debug Mode
```berry
# Enable debug output
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
# Check generated code
try
var berry_code = animation.compile_dsl(dsl_source)
print("Generated Berry code:")
print(berry_code)
except "dsl_compilation_error" as e, msg
print("Compilation error:", msg)
end
```
### Step-by-Step Testing
```berry
# Test each component individually
print("1. Creating strip...")
var strip = Leds(30)
print("Strip created:", strip != nil)
print("2. Creating engine...")
var engine = animation.create_engine(strip)
print("Engine created:", engine != nil)
print("3. Creating animation...")
var anim = animation.solid(0xFFFF0000)
print("Animation created:", anim != nil)
print("4. Adding animation...")
engine.add_animation(anim)
print("Animation count:", engine.size())
print("5. Starting engine...")
engine.start()
print("Engine active:", engine.is_active())
```
### Monitor Performance
```berry
# Check timing
var start_time = tasmota.millis()
# ... run animation code ...
var end_time = tasmota.millis()
print("Execution time:", end_time - start_time, "ms")
# Monitor memory (if available)
import gc
print("Memory before:", gc.allocated())
# ... create animations ...
print("Memory after:", gc.allocated())
```
## Getting Help
### Information to Provide
When asking for help, include:
1. **Hardware Setup:**
- LED strip type and count
- GPIO pin used
- Power supply specifications
2. **Software Environment:**
- Tasmota version
- Berry version
- Framework version
3. **Code:**
- Complete minimal example that reproduces the issue
- Error messages (exact text)
- Expected vs. actual behavior
4. **Debugging Output:**
- Debug mode output
- Generated Berry code (for DSL issues)
- Console output
### Example Bug Report
```
**Problem:** DSL animation compiles but LEDs don't change
**Hardware:**
- 30x WS2812 LEDs on GPIO 1
- ESP32 with 5V/2A power supply
**Code:**
```dsl
color red = #FF0000
animation red_anim = solid(red)
run red_anim
```
**Error Output:**
```
DSL compilation successful
Engine created: true
Animation count: 1
Engine active: true
```
**Expected:** LEDs turn red
**Actual:** LEDs remain off
**Additional Info:**
- Basic `strip.set_pixel_color(0, 0xFFFF0000); strip.show()` works
- Tasmota 13.2.0, Berry enabled
```
This format helps identify issues quickly and provide targeted solutions.
## Prevention Tips
### Code Quality
1. **Use Try-Catch Blocks:**
```berry
try
runtime.load_dsl(dsl_code)
except .. as e, msg
print("Error:", msg)
end
```
2. **Validate Inputs:**
```berry
if type(color) == "int" && color >= 0
var anim = animation.solid(color)
else
print("Invalid color:", color)
end
```
3. **Test Incrementally:**
- Start with simple solid colors
- Add one effect at a time
- Test each change before proceeding
### Performance Best Practices
1. **Limit Complexity:**
- 1-3 simultaneous animations
- Reasonable animation periods (>1 second)
- Moderate palette sizes
2. **Resource Management:**
- Clear unused animations
- Reuse value providers
- Use sequences for complex shows
3. **Hardware Considerations:**
- Adequate power supply
- Proper wiring and connections
- Appropriate LED strip for application
Following these guidelines will help you avoid most common issues and create reliable LED animations.

View File

@ -0,0 +1,542 @@
# DSL Lexer (Tokenizer) for Animation DSL
# Converts DSL source code into a stream of tokens for the single-pass transpiler
# Get reference to animation module (avoid circular import)
#@ solidify:DSLLexer,weak
class DSLLexer
var source # String - DSL source code
var position # Integer - current character position
var line # Integer - current line number (1-based)
var column # Integer - current column number (1-based)
var tokens # List - generated tokens
var errors # List - lexical errors encountered
# Initialize lexer with source code
#
# @param source: string - DSL source code to tokenize
def init(source)
self.source = source != nil ? source : ""
self.position = 0
self.line = 1
self.column = 1
self.tokens = []
self.errors = []
end
# Tokenize the entire source code
#
# @return list - Array of Token objects
def tokenize()
self.tokens = []
self.errors = []
self.position = 0
self.line = 1
self.column = 1
while !self.at_end()
self.scan_token()
end
# Add EOF token
self.add_token(38 #-animation.Token.EOF-#, "", 0)
return self.tokens
end
# Scan and create the next token
def scan_token()
var start_column = self.column
var ch = self.advance()
if ch == ' ' || ch == '\t' || ch == '\r'
# Skip whitespace (but not newlines - they can be significant)
return
elif ch == '\n'
self.add_token(35 #-animation.Token.NEWLINE-#, "\n", 1)
self.line += 1
self.column = 1
return
elif ch == '#'
self.scan_comment_or_color()
elif self.is_alpha(ch) || ch == '_'
self.scan_identifier_or_keyword()
elif self.is_digit(ch)
self.scan_number()
elif ch == '"' || ch == "'"
self.scan_string(ch)
elif ch == '$'
self.scan_variable_reference()
else
self.scan_operator_or_delimiter(ch)
end
end
# Scan comment or hex color (both start with #)
def scan_comment_or_color()
var start_pos = self.position - 1
var start_column = self.column - 1
# Look ahead to see if this is a hex color
if self.position < size(self.source) && self.is_hex_digit(self.source[self.position])
# This is a hex color
self.scan_hex_color()
else
# This is a comment - consume until end of line
while !self.at_end() && self.peek() != '\n'
self.advance()
end
var comment_text = self.source[start_pos..self.position-1]
self.add_token(37 #-animation.Token.COMMENT-#, comment_text, self.position - start_pos)
end
end
# Scan hex color (#RRGGBB, #RGB, #AARRGGBB, or #ARGB)
def scan_hex_color()
var start_pos = self.position - 1 # Include the #
var start_column = self.column - 1
var hex_digits = 0
# Count hex digits
while !self.at_end() && self.is_hex_digit(self.peek())
self.advance()
hex_digits += 1
end
var color_value = self.source[start_pos..self.position-1]
# Validate hex color format - support alpha channel
if hex_digits == 3 || hex_digits == 4 || hex_digits == 6 || hex_digits == 8
self.add_token(4 #-animation.Token.COLOR-#, color_value, size(color_value))
else
self.add_error("Invalid hex color format: " + color_value)
self.add_token(39 #-animation.Token.ERROR-#, color_value, size(color_value))
end
end
# Scan identifier or keyword
def scan_identifier_or_keyword()
var start_pos = self.position - 1
var start_column = self.column - 1
# Continue while alphanumeric or underscore
while !self.at_end() && (self.is_alnum(self.peek()) || self.peek() == '_')
self.advance()
end
var text = self.source[start_pos..self.position-1]
var token_type
# Check for color names first (they take precedence over keywords)
if animation.is_color_name(text)
token_type = 4 #-animation.Token.COLOR-#
elif animation.is_keyword(text)
token_type = 0 #-animation.Token.KEYWORD-#
else
token_type = 1 #-animation.Token.IDENTIFIER-#
end
self.add_token(token_type, text, size(text))
end
# Scan numeric literal (with optional time/percentage/multiplier suffix)
def scan_number()
var start_pos = self.position - 1
var start_column = self.column - 1
var has_dot = false
# Scan integer part
while !self.at_end() && self.is_digit(self.peek())
self.advance()
end
# Check for decimal point
if !self.at_end() && self.peek() == '.' &&
self.position + 1 < size(self.source) && self.is_digit(self.source[self.position + 1])
has_dot = true
self.advance() # consume '.'
# Scan fractional part
while !self.at_end() && self.is_digit(self.peek())
self.advance()
end
end
var number_text = self.source[start_pos..self.position-1]
# Check for time unit suffixes
if self.check_time_suffix()
var suffix = self.scan_time_suffix()
self.add_token(5 #-animation.Token.TIME-#, number_text + suffix, size(number_text + suffix))
# Check for percentage suffix
elif !self.at_end() && self.peek() == '%'
self.advance()
self.add_token(6 #-animation.Token.PERCENTAGE-#, number_text + "%", size(number_text) + 1)
# Check for multiplier suffix
elif !self.at_end() && self.peek() == 'x'
self.advance()
self.add_token(7 #-animation.Token.MULTIPLIER-#, number_text + "x", size(number_text) + 1)
else
# Plain number
self.add_token(2 #-animation.Token.NUMBER-#, number_text, size(number_text))
end
end
# Check if current position has a time suffix
def check_time_suffix()
import string
if self.at_end()
return false
end
var remaining = self.source[self.position..]
return string.startswith(remaining, "ms") ||
string.startswith(remaining, "s") ||
string.startswith(remaining, "m") ||
string.startswith(remaining, "h")
end
# Scan time suffix and return it
def scan_time_suffix()
import string
if string.startswith(self.source[self.position..], "ms")
self.advance()
self.advance()
return "ms"
elif self.peek() == 's'
self.advance()
return "s"
elif self.peek() == 'm'
self.advance()
return "m"
elif self.peek() == 'h'
self.advance()
return "h"
end
return ""
end
# Scan string literal
def scan_string(quote_char)
var start_pos = self.position - 1 # Include opening quote
var start_column = self.column - 1
var value = ""
while !self.at_end() && self.peek() != quote_char
var ch = self.advance()
if ch == '\\'
# Handle escape sequences
if !self.at_end()
var escaped = self.advance()
if escaped == 'n'
value += '\n'
elif escaped == 't'
value += '\t'
elif escaped == 'r'
value += '\r'
elif escaped == '\\'
value += '\\'
elif escaped == quote_char
value += quote_char
else
# Unknown escape sequence - include as-is
value += '\\'
value += escaped
end
else
value += '\\'
end
elif ch == '\n'
self.line += 1
self.column = 1
value += ch
else
value += ch
end
end
if self.at_end()
self.add_error("Unterminated string literal")
self.add_token(39 #-animation.Token.ERROR-#, value, self.position - start_pos)
else
# Consume closing quote
self.advance()
self.add_token(3 #-animation.Token.STRING-#, value, self.position - start_pos)
end
end
# Scan variable reference ($identifier)
def scan_variable_reference()
var start_pos = self.position - 1 # Include $
var start_column = self.column - 1
if self.at_end() || !(self.is_alpha(self.peek()) || self.peek() == '_')
self.add_error("Invalid variable reference: $ must be followed by identifier")
self.add_token(39 #-animation.Token.ERROR-#, "$", 1)
return
end
# Scan identifier part
while !self.at_end() && (self.is_alnum(self.peek()) || self.peek() == '_')
self.advance()
end
var var_ref = self.source[start_pos..self.position-1]
self.add_token(36 #-animation.Token.VARIABLE_REF-#, var_ref, size(var_ref))
end
# Scan operator or delimiter
def scan_operator_or_delimiter(ch)
var start_column = self.column - 1
if ch == '='
if self.match('=')
self.add_token(15 #-animation.Token.EQUAL-#, "==", 2)
else
self.add_token(8 #-animation.Token.ASSIGN-#, "=", 1)
end
elif ch == '!'
if self.match('=')
self.add_token(16 #-animation.Token.NOT_EQUAL-#, "!=", 2)
else
self.add_token(23 #-animation.Token.LOGICAL_NOT-#, "!", 1)
end
elif ch == '<'
if self.match('=')
self.add_token(18 #-animation.Token.LESS_EQUAL-#, "<=", 2)
elif self.match('<')
# Left shift - not used in DSL but included for completeness
self.add_token(39 #-animation.Token.ERROR-#, "<<", 2)
else
self.add_token(17 #-animation.Token.LESS_THAN-#, "<", 1)
end
elif ch == '>'
if self.match('=')
self.add_token(20 #-animation.Token.GREATER_EQUAL-#, ">=", 2)
elif self.match('>')
# Right shift - not used in DSL but included for completeness
self.add_token(39 #-animation.Token.ERROR-#, ">>", 2)
else
self.add_token(19 #-animation.Token.GREATER_THAN-#, ">", 1)
end
elif ch == '&'
if self.match('&')
self.add_token(21 #-animation.Token.LOGICAL_AND-#, "&&", 2)
else
self.add_error("Single '&' not supported in DSL")
self.add_token(39 #-animation.Token.ERROR-#, "&", 1)
end
elif ch == '|'
if self.match('|')
self.add_token(22 #-animation.Token.LOGICAL_OR-#, "||", 2)
else
self.add_error("Single '|' not supported in DSL")
self.add_token(39 #-animation.Token.ERROR-#, "|", 1)
end
elif ch == '-'
if self.match('>')
self.add_token(34 #-animation.Token.ARROW-#, "->", 2)
else
self.add_token(10 #-animation.Token.MINUS-#, "-", 1)
end
elif ch == '+'
self.add_token(9 #-animation.Token.PLUS-#, "+", 1)
elif ch == '*'
self.add_token(11 #-animation.Token.MULTIPLY-#, "*", 1)
elif ch == '/'
self.add_token(12 #-animation.Token.DIVIDE-#, "/", 1)
elif ch == '%'
self.add_token(13 #-animation.Token.MODULO-#, "%", 1)
elif ch == '^'
self.add_token(14 #-animation.Token.POWER-#, "^", 1)
elif ch == '('
self.add_token(24 #-animation.Token.LEFT_PAREN-#, "(", 1)
elif ch == ')'
self.add_token(25 #-animation.Token.RIGHT_PAREN-#, ")", 1)
elif ch == '{'
self.add_token(26 #-animation.Token.LEFT_BRACE-#, "{", 1)
elif ch == '}'
self.add_token(27 #-animation.Token.RIGHT_BRACE-#, "}", 1)
elif ch == '['
self.add_token(28 #-animation.Token.LEFT_BRACKET-#, "[", 1)
elif ch == ']'
self.add_token(29 #-animation.Token.RIGHT_BRACKET-#, "]", 1)
elif ch == ','
self.add_token(30 #-animation.Token.COMMA-#, ",", 1)
elif ch == ';'
self.add_token(31 #-animation.Token.SEMICOLON-#, ";", 1)
elif ch == ':'
self.add_token(32 #-animation.Token.COLON-#, ":", 1)
elif ch == '.'
if self.match('.')
# Range operator (..) - treat as two dots for now
self.add_token(33 #-animation.Token.DOT-#, ".", 1)
self.add_token(33 #-animation.Token.DOT-#, ".", 1)
else
self.add_token(33 #-animation.Token.DOT-#, ".", 1)
end
else
self.add_error("Unexpected character: '" + ch + "'")
self.add_token(39 #-animation.Token.ERROR-#, ch, 1)
end
end
# Helper methods
# Check if at end of source
def at_end()
return self.position >= size(self.source)
end
# Advance position and return current character
def advance()
if self.at_end()
return ""
end
var ch = self.source[self.position]
self.position += 1
self.column += 1
return ch
end
# Peek at current character without advancing
def peek()
if self.at_end()
return ""
end
return self.source[self.position]
end
# Peek at next character without advancing
def peek_next()
if self.position + 1 >= size(self.source)
return ""
end
return self.source[self.position + 1]
end
# Check if current character matches expected and advance if so
def match(expected)
if self.at_end() || self.source[self.position] != expected
return false
end
self.position += 1
self.column += 1
return true
end
# Character classification helpers
def is_alpha(ch)
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
end
def is_digit(ch)
return ch >= '0' && ch <= '9'
end
def is_alnum(ch)
return self.is_alpha(ch) || self.is_digit(ch)
end
def is_hex_digit(ch)
return self.is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
end
# Add token to tokens list
def add_token(token_type, value, length)
var token = animation.Token(token_type, value, self.line, self.column - length, length)
self.tokens.push(token)
end
# Add error to errors list
def add_error(message)
self.errors.push({
"message": message,
"line": self.line,
"column": self.column,
"position": self.position
})
end
# Get all errors encountered during tokenization
def get_errors()
return self.errors
end
# Check if any errors were encountered
def has_errors()
return size(self.errors) > 0
end
# Get a formatted error report
def get_error_report()
if !self.has_errors()
return "No lexical errors"
end
var report = "Lexical errors (" + str(size(self.errors)) + "):\n"
for error : self.errors
report += " Line " + str(error["line"]) + ":" + str(error["column"]) + ": " + error["message"] + "\n"
end
return report
end
# Reset lexer state for reuse
def reset(new_source)
self.source = new_source != nil ? new_source : ""
self.position = 0
self.line = 1
self.column = 1
self.tokens = []
self.errors = []
end
# Get current position info for debugging
def get_position_info()
return {
"position": self.position,
"line": self.line,
"column": self.column,
"at_end": self.at_end()
}
end
# Tokenize and return both tokens and errors
def tokenize_with_errors()
var tokens = self.tokenize()
var result = {
"tokens": tokens,
"errors": self.errors,
"success": !self.has_errors()
}
return result
end
end
# Utility function to tokenize DSL source code
#
# @param source: string - DSL source code
# @return list - Array of Token objects
def tokenize_dsl(source)
var lexer = animation.DSLLexer(source)
return lexer.tokenize()
end
# Utility function to tokenize with error handling
#
# @param source: string - DSL source code
# @return map - {tokens: list, errors: list, success: bool}
def tokenize_dsl_with_errors(source)
var lexer = animation.DSLLexer(source)
return lexer.tokenize_with_errors()
end
return {
"DSLLexer": DSLLexer,
"tokenize_dsl": tokenize_dsl,
"tokenize_dsl_with_errors": tokenize_dsl_with_errors
}

View File

@ -0,0 +1,176 @@
# DSL Runtime Integration
# Provides complete DSL execution lifecycle management
#@ solidify:DSLRuntime,weak
class DSLRuntime
var engine # Animation engine instance
var active_source # Currently loaded DSL source
var debug_mode # Enable debug output
def init(engine, debug_mode)
self.engine = engine
self.active_source = nil
self.debug_mode = debug_mode != nil ? debug_mode : false
end
# Load and execute DSL from string
def load_dsl(source_code)
if source_code == nil || size(source_code) == 0
if self.debug_mode
print("DSL: Empty source code")
end
return false
end
# Compile DSL with exception handling
if self.debug_mode
print("DSL: Compiling source...")
end
try
var berry_code = animation.compile_dsl(source_code)
# Execute the compiled Berry code
return self.execute_berry_code(berry_code, source_code)
except "dsl_compilation_error" as e, msg
if self.debug_mode
print("DSL: Compilation failed - " + msg)
end
return false
end
end
# Load DSL from file
def load_dsl_file(filename)
try
var file = open(filename, "r")
if file == nil
if self.debug_mode
print(f"DSL: Cannot open file {filename}")
end
return false
end
var source_code = file.read()
file.close()
if self.debug_mode
print(f"DSL: Loaded {size(source_code)} characters from {filename}")
end
return self.load_dsl(source_code)
except .. as e, msg
if self.debug_mode
print(f"DSL: File loading error: {msg}")
end
return false
end
end
# Reload current DSL (useful for development)
def reload_dsl()
if self.active_source == nil
if self.debug_mode
print("DSL: No active DSL to reload")
end
return false
end
if self.debug_mode
print("DSL: Reloading current DSL...")
end
# Stop current animations
self.engine.stop()
self.engine.clear()
# Reload with fresh compilation
return self.load_dsl(self.active_source)
end
# Get generated Berry code for inspection (debugging)
def get_generated_code(source_code)
if source_code == nil
source_code = self.active_source
end
if source_code == nil
return nil
end
# Generate code with exception handling
try
return animation.compile_dsl(source_code)
except "dsl_compilation_error" as e, msg
if self.debug_mode
print("DSL: Code generation failed - " + msg)
end
return nil
end
end
# Execute Berry code with proper error handling
def execute_berry_code(berry_code, source_code)
try
# Stop current animations before starting new ones
self.engine.stop()
self.engine.clear()
# Compile and execute the Berry code
var compiled_func = compile(berry_code)
if compiled_func == nil
if self.debug_mode
print("DSL: Berry compilation failed")
end
return false
end
# Execute in controlled environment
compiled_func()
# Store as active source
self.active_source = source_code
if self.debug_mode
print("DSL: Execution successful")
end
return true
except .. as e, msg
if self.debug_mode
print(f"DSL: Execution error: {msg}")
end
return false
end
end
# Get current engine for external access
def get_controller()
return self.engine
end
# Check if DSL is currently loaded
def is_loaded()
return self.active_source != nil
end
# Get current DSL source
def get_active_source()
return self.active_source
end
end
# Factory function for easy creation
def create_dsl_runtime(strip, debug_mode)
var engine = animation.create_engine(strip)
return animation.DSLRuntime(engine, debug_mode)
end
# Return module exports
return {
"DSLRuntime": DSLRuntime,
"create_dsl_runtime": create_dsl_runtime
}

View File

@ -0,0 +1,508 @@
# Token Types and Token Class for Animation DSL
# Defines all token types and the Token class with line/column tracking
#@ solidify:Token,weak
class Token
# Basic token types
static var KEYWORD = 0 # strip, color, pattern, animation, sequence, etc.
static var IDENTIFIER = 1 # user-defined names
static var NUMBER = 2 # 123, 3.14
static var STRING = 3 # "hello", 'world'
static var COLOR = 4 # #FF0000, rgb(255,0,0), hsv(240,100,100)
static var TIME = 5 # 2s, 500ms, 1m, 2h
static var PERCENTAGE = 6 # 50%, 100%
static var MULTIPLIER = 7 # 2x, 0.5x
# Static arrays for better solidification (moved from inline arrays)
static var names = [
"KEYWORD", "IDENTIFIER", "NUMBER", "STRING", "COLOR", "TIME", "PERCENTAGE", "MULTIPLIER",
"ASSIGN", "PLUS", "MINUS", "MULTIPLY", "DIVIDE", "MODULO", "POWER",
"EQUAL", "NOT_EQUAL", "LESS_THAN", "LESS_EQUAL", "GREATER_THAN", "GREATER_EQUAL",
"LOGICAL_AND", "LOGICAL_OR", "LOGICAL_NOT",
"LEFT_PAREN", "RIGHT_PAREN", "LEFT_BRACE", "RIGHT_BRACE", "LEFT_BRACKET", "RIGHT_BRACKET",
"COMMA", "SEMICOLON", "COLON", "DOT", "ARROW",
"NEWLINE", "VARIABLE_REF", "COMMENT", "EOF", "ERROR",
"EVENT_ON", "EVENT_INTERRUPT", "EVENT_RESUME", "EVENT_AFTER"
]
static var statement_keywords = [
"strip", "set", "color", "palette", "pattern", "animation",
"sequence", "function", "zone", "on", "run"
]
static var keywords = [
# Configuration keywords
"strip", "set",
# Definition keywords
"color", "palette", "pattern", "animation", "sequence", "function", "zone",
# Control flow keywords
"play", "for", "with", "repeat", "times", "forever", "if", "else", "elif",
"choose", "random", "on", "run", "wait", "goto", "interrupt", "resume",
"while", "from", "to", "return",
# Modifier keywords
"at", "opacity", "offset", "speed", "weight", "ease", "sync", "every",
"stagger", "across", "pixels",
# Core built-in functions (minimal set for essential DSL operations)
"rgb", "hsv",
# Spatial keywords
"all", "even", "odd", "center", "edges", "left", "right", "top", "bottom",
# Boolean and special values
"true", "false", "nil", "transparent",
# Event keywords
"startup", "shutdown", "button_press", "button_hold", "motion_detected",
"brightness_change", "timer", "time", "sound_peak", "network_message",
# Time and measurement keywords
"ms", "s", "m", "h", "bpm"
]
static var color_names = [
"red", "green", "blue", "white", "black", "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"
]
# Operators
static var ASSIGN = 8 # =
static var PLUS = 9 # +
static var MINUS = 10 # -
static var MULTIPLY = 11 # *
static var DIVIDE = 12 # /
static var MODULO = 13 # %
static var POWER = 14 # ^
# Comparison operators
static var EQUAL = 15 # ==
static var NOT_EQUAL = 16 # !=
static var LESS_THAN = 17 # <
static var LESS_EQUAL = 18 # <=
static var GREATER_THAN = 19 # >
static var GREATER_EQUAL = 20 # >=
# Logical operators
static var LOGICAL_AND = 21 # &&
static var LOGICAL_OR = 22 # ||
static var LOGICAL_NOT = 23 # !
# Delimiters
static var LEFT_PAREN = 24 # (
static var RIGHT_PAREN = 25 # )
static var LEFT_BRACE = 26 # {
static var RIGHT_BRACE = 27 # }
static var LEFT_BRACKET = 28 # [
static var RIGHT_BRACKET = 29 # ]
# Separators
static var COMMA = 30 # ,
static var SEMICOLON = 31 # ;
static var COLON = 32 # :
static var DOT = 33 # .
static var ARROW = 34 # ->
# Special tokens
static var NEWLINE = 35 # \n (significant in some contexts)
static var VARIABLE_REF = 36 # $identifier
static var COMMENT = 37 # # comment text
static var EOF = 38 # End of file
static var ERROR = 39 # Error token for invalid input
# Event-related tokens
static var EVENT_ON = 40 # on (event handler keyword)
static var EVENT_INTERRUPT = 41 # interrupt
static var EVENT_RESUME = 42 # resume
static var EVENT_AFTER = 43 # after (for resume timing)
# Convert token type to string for debugging
static def to_string(token_type)
if token_type >= 0 && token_type < size(_class.names)
return _class.names[token_type]
end
return "UNKNOWN"
end
var type # int - the type of this token (Token.KEYWORD, Token.IDENTIFIER, etc.)
var value # String - the actual text value of the token
var line # Integer - line number where token appears (1-based)
var column # Integer - column number where token starts (1-based)
var length # Integer - length of the token in characters
# Initialize a new token
#
# @param type: int - Token type constant (Token.KEYWORD, Token.IDENTIFIER, etc.)
# @param value: string - The actual text value
# @param line: int - Line number (1-based)
# @param column: int - Column number (1-based)
# @param length: int - Length of token in characters (optional, defaults to value length)
def init(typ, value, line, column, length)
self.type = typ
self.value = value != nil ? value : ""
self.line = line != nil ? line : 1
self.column = column != nil ? column : 1
self.length = length != nil ? length : size(self.value)
end
# Check if this token is of a specific type
#
# @param token_type: int - Token type to check against
# @return bool - True if token matches the type
def is_type(token_type)
return self.type == token_type
end
# Check if this token is a keyword with specific value
#
# @param keyword: string - Keyword to check for
# @return bool - True if token is the specified keyword
def is_keyword(keyword)
return self.type == 0 #-self.KEYWORD-# && self.value == keyword
end
# Check if this token is an identifier with specific value
#
# @param name: string - Identifier name to check for
# @return bool - True if token is the specified identifier
def is_identifier(name)
return self.type == 1 #-self.IDENTIFIER-# && self.value == name
end
# Check if this token is an operator
#
# @return bool - True if token is any operator type
def is_operator()
return self.type >= 8 #-self.ASSIGN-# && self.type <= 23 #-self.LOGICAL_NOT-#
end
# Check if this token is a delimiter
#
# @return bool - True if token is any delimiter type
def is_delimiter()
return self.type >= 24 #-self.LEFT_PAREN-# && self.type <= 29 #-self.RIGHT_BRACKET-#
end
# Check if this token is a separator
#
# @return bool - True if token is any separator type
def is_separator()
return self.type >= 30 #-self.COMMA-# && self.type <= 34 #-self.ARROW-#
end
# Check if this token is a literal value
#
# @return bool - True if token represents a literal value
def is_literal()
return self.type == 2 #-self.NUMBER-# ||
self.type == 3 #-self.STRING-# ||
self.type == 4 #-self.COLOR-# ||
self.type == 5 #-self.TIME-# ||
self.type == 6 #-self.PERCENTAGE-# ||
self.type == 7 #-self.MULTIPLIER-#
end
# Get the end column of this token
#
# @return int - Column number where token ends
def end_column()
return self.column + self.length - 1
end
# Create a copy of this token with a different type
#
# @param new_type: int - New token type
# @return Token - New token with same position but different type
def with_type(new_type)
return animation.Token(new_type, self.value, self.line, self.column, self.length)
end
# Create a copy of this token with a different value
#
# @param new_value: string - New value
# @return Token - New token with same position but different value
def with_value(new_value)
return animation.Token(self.type, new_value, self.line, self.column, size(new_value))
end
# Get a string representation of the token for debugging
#
# @return string - Human-readable token description
def tostring()
var type_name = self.to_string(self.type)
if self.type == 38 #-self.EOF-#
return f"Token({type_name} at {self.line}:{self.column})"
elif self.type == 35 #-self.NEWLINE-#
return f"Token({type_name} at {self.line}:{self.column})"
elif size(self.value) > 20
var short_value = self.value[0..17] + "..."
return f"Token({type_name}, '{short_value}' at {self.line}:{self.column})"
else
return f"Token({type_name}, '{self.value}' at {self.line}:{self.column})"
end
end
# Get a compact string representation for error messages
#
# @return string - Compact token description
def to_error_string()
if self.type == 38 #-self.EOF-#
return "end of file"
elif self.type == 35 #-self.NEWLINE-#
return "newline"
elif self.type == 0 #-self.KEYWORD-#
return f"keyword '{self.value}'"
elif self.type == 1 #-self.IDENTIFIER-#
return f"identifier '{self.value}'"
elif self.type == 3 #-self.STRING-#
return f"string '{self.value}'"
elif self.type == 2 #-self.NUMBER-#
return f"number '{self.value}'"
elif self.type == 4 #-self.COLOR-#
return f"color '{self.value}'"
elif self.type == 5 #-self.TIME-#
return f"time '{self.value}'"
elif self.type == 6 #-self.PERCENTAGE-#
return f"percentage '{self.value}'"
elif self.type == 39 #-self.ERROR-#
return f"invalid token '{self.value}'"
else
return f"'{self.value}'"
end
end
# Check if this token represents a boolean value
#
# @return bool - True if token is "true" or "false" keyword
def is_boolean()
return self.type == 0 #-self.KEYWORD-# && (self.value == "true" || self.value == "false")
end
# Get boolean value if this token represents one
#
# @return bool - Boolean value, or nil if not a boolean token
def get_boolean_value()
if self.is_boolean()
return self.value == "true"
end
return nil
end
# Check if this token represents a numeric value
#
# @return bool - True if token can be converted to a number
def is_numeric()
return self.type == 2 #-self.NUMBER-# ||
self.type == 5 #-self.TIME-# ||
self.type == 6 #-self.PERCENTAGE-# ||
self.type == 7 #-self.MULTIPLIER-#
end
# Get numeric value from token (without units) - returns only integers
#
# @return int - Numeric value, or nil if not numeric
# - time is in ms
# - percentage is converted to 100% = 255
# - times is converted to x256 (2x = 512)
def get_numeric_value()
import string
import math
if self.type == 2 #-self.NUMBER-#
return math.round(real(self.value))
elif self.type == 5 #-self.TIME-#
# Remove time unit suffix and convert to milliseconds
var value_str = self.value
if string.endswith(value_str, "ms")
return math.round(real(value_str[0..-3]))
elif string.endswith(value_str, "s")
return math.round(real(value_str[0..-2]) * 1000)
elif string.endswith(value_str, "m")
return math.round(real(value_str[0..-2]) * 60000)
elif string.endswith(value_str, "h")
return math.round(real(value_str[0..-2]) * 3600000)
end
elif self.type == 6 #-self.PERCENTAGE-#
# Remove % and convert to 0-255 range (100% = 255)
var percent = math.round(real(self.value[0..-2]))
return tasmota.scale_uint(percent, 0, 100, 0, 255)
elif self.type == 7 #-self.MULTIPLIER-#
# Remove x suffix and convert to x256 scale (2x = 512)
var multiplier = real(self.value[0..-2])
return math.round(multiplier * 256)
end
return nil
end
# Check if this token can start an expression
#
# @return bool - True if token can begin an expression
def can_start_expression()
return self.is_literal() ||
self.type == 1 #-self.IDENTIFIER-# ||
self.type == 36 #-self.VARIABLE_REF-# ||
self.type == 24 #-self.LEFT_PAREN-# ||
self.type == 23 #-self.LOGICAL_NOT-# ||
self.type == 10 #-self.MINUS-# ||
self.type == 9 #-self.PLUS-#
end
# Check if this token can end an expression
#
# @return bool - True if token can end an expression
def can_end_expression()
return self.is_literal() ||
self.type == 1 #-self.IDENTIFIER-# ||
self.type == 36 #-self.VARIABLE_REF-# ||
self.type == 25 #-self.RIGHT_PAREN-#
end
# Check if this token indicates the start of a new top-level statement
# Useful for single-pass transpiler to know when to stop collecting expression tokens
#
# @return bool - True if token starts a new statement
def is_statement_start()
if self.type != 0 #-self.KEYWORD-#
return false
end
for keyword : self.statement_keywords
if self.value == keyword
return true
end
end
return false
end
# Check if this token is a DSL function name (for pattern/animation expressions)
# Uses dynamic introspection to check if function exists in animation module
#
# @return bool - True if token is a DSL function name
def is_dsl_function()
if self.type != 0 #-self.KEYWORD-#
return false
end
# Use dynamic introspection to check if function exists in animation module
# This automatically supports any new functions added to the framework
try
import introspect
var animation = global.animation
if animation != nil
var members = introspect.members(animation)
return members.find(self.value) != nil
end
except .. as e, msg
# Fallback to false if introspection fails
return false
end
return false
end
end
# Utility functions for token handling
# Create an EOF token at a specific position
#
# @param line: int - Line number
# @param column: int - Column number
# @return Token - EOF token
def create_eof_token(line, column)
return animation.Token(38 #-animation.Token.EOF-#, "", line, column, 0)
end
# Create an error token with a message
#
# @param message: string - Error message
# @param line: int - Line number
# @param column: int - Column number
# @return Token - Error token
def create_error_token(message, line, column)
return animation.Token(39 #-animation.Token.ERROR-#, message, line, column, size(message))
end
# Create a newline token
#
# @param line: int - Line number
# @param column: int - Column number
# @return Token - Newline token
def create_newline_token(line, column)
return animation.Token(35 #-animation.Token.NEWLINE-#, "\n", line, column, 1)
end
# Check if a string is a reserved keyword
#
# @param word: string - Word to check
# @return bool - True if word is a reserved keyword
def is_keyword(word)
for keyword : animation.Token.keywords
if word == keyword
return true
end
end
return false
end
# Check if a string is a predefined color name
#
# @param word: string - Word to check
# @return bool - True if word is a predefined color name
def is_color_name(word)
for color : animation.Token.color_names
if word == color
return true
end
end
return false
end
# Get the precedence of an operator token
#
# @param token: Token - Operator token
# @return int - Precedence level (higher number = higher precedence)
def get_operator_precedence(token)
if token.type == 22 #-animation.Token.LOGICAL_OR-#
return 1
elif token.type == 21 #-animation.Token.LOGICAL_AND-#
return 2
elif token.type == 15 #-animation.Token.EQUAL-# || token.type == 16 #-animation.Token.NOT_EQUAL-#
return 3
elif token.type == 17 #-animation.Token.LESS_THAN-# || token.type == 18 #-animation.Token.LESS_EQUAL-# ||
token.type == 19 #-animation.Token.GREATER_THAN-# || token.type == 20 #-animation.Token.GREATER_EQUAL-#
return 4
elif token.type == 9 #-animation.Token.PLUS-# || token.type == 10 #-animation.Token.MINUS-#
return 5
elif token.type == 11 #-animation.Token.MULTIPLY-# || token.type == 12 #-animation.Token.DIVIDE-# || token.type == 13 #-animation.Token.MODULO-#
return 6
elif token.type == 14 #-animation.Token.POWER-#
return 7
end
return 0 # Not an operator or unknown operator
end
# Check if an operator is right-associative
#
# @param token: Token - Operator token
# @return bool - True if operator is right-associative
def is_right_associative(token)
return token.type == 14 #-animation.Token.POWER-# # Only power operator is right-associative
end
return {
"Token": Token,
"create_eof_token": create_eof_token,
"create_error_token": create_error_token,
"create_newline_token": create_newline_token,
"is_keyword": is_keyword,
"is_color_name": is_color_name,
"get_operator_precedence": get_operator_precedence,
"is_right_associative": is_right_associative
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,319 @@
# 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
var source_animation # Source animation to bounce
var bounce_speed # Initial bounce speed (0-255)
var bounce_range # Bounce range in pixels (0 = full strip)
var damping # Damping factor (0-255, 255 = no damping)
var gravity # Gravity effect (0-255)
var strip_length # Length of the LED strip
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
# Initialize a new Bounce animation
#
# @param source_animation: Animation - Source animation to bounce
# @param bounce_speed: int - Initial bounce speed (0-255), defaults to 128 if nil
# @param bounce_range: int - Bounce range in pixels (0=full strip), defaults to 0 if nil
# @param damping: int - Damping factor (0-255), defaults to 250 if nil
# @param gravity: int - Gravity effect (0-255), defaults to 0 if nil
# @param strip_length: int - Length of LED strip, defaults to 30 if nil
# @param priority: int - Rendering priority, defaults to 10 if nil
# @param duration: int - Duration in ms, defaults to 0 (infinite) if nil
# @param loop: bool - Whether to loop, defaults to true if nil
# @param name: string - Animation name, defaults to "bounce" if nil
def init(source_animation, bounce_speed, bounce_range, damping, gravity, strip_length, priority, duration, loop, name)
# Call parent constructor
super(self).init(priority, duration, loop != nil ? loop : true, 255, name != nil ? name : "bounce")
# Set parameters with defaults
self.source_animation = source_animation
self.bounce_speed = bounce_speed != nil ? bounce_speed : 128
self.bounce_range = bounce_range != nil ? bounce_range : 0
self.damping = damping != nil ? damping : 250
self.gravity = gravity != nil ? gravity : 0
self.strip_length = strip_length != nil ? strip_length : 30
# Calculate bounce parameters
var effective_range = self.bounce_range > 0 ? self.bounce_range : self.strip_length
self.bounce_center = self.strip_length * 256 / 2 # Center in 1/256th pixels
# Initialize physics state
self.current_position = self.bounce_center
# Speed: 0-255 maps to 0-20 pixels per second
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
self.source_frame = animation.frame_buffer(self.strip_length)
self.current_colors = []
self.current_colors.resize(self.strip_length)
self.last_update_time = 0
# Initialize colors to black
var i = 0
while i < self.strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
# Register parameters
self.register_param("bounce_speed", {"min": 0, "max": 255, "default": 128})
self.register_param("bounce_range", {"min": 0, "max": 1000, "default": 0})
self.register_param("damping", {"min": 0, "max": 255, "default": 250})
self.register_param("gravity", {"min": 0, "max": 255, "default": 0})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("bounce_speed", self.bounce_speed)
self.set_param("bounce_range", self.bounce_range)
self.set_param("damping", self.damping)
self.set_param("gravity", self.gravity)
self.set_param("strip_length", self.strip_length)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "bounce_speed"
self.bounce_speed = value
# 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
elif name == "bounce_range"
self.bounce_range = value
elif name == "damping"
self.damping = value
elif name == "gravity"
self.gravity = value
elif name == "strip_length"
self.strip_length = value
self.current_colors.resize(value)
self.source_frame = animation.frame_buffer(value)
self.bounce_center = value * 256 / 2
var i = 0
while i < value
if self.current_colors[i] == nil
self.current_colors[i] = 0xFF000000
end
i += 1
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
if self.source_animation != nil
if !self.source_animation.is_running
self.source_animation.start(self.start_time)
end
self.source_animation.update(time_ms)
end
# Calculate bounced colors
self._calculate_bounce()
return true
end
# Update bounce physics
def _update_physics(dt_ms)
# Use integer arithmetic for physics (dt in milliseconds)
# Apply gravity (downward acceleration)
if self.gravity > 0
var gravity_accel = tasmota.scale_uint(self.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 = self.bounce_range > 0 ? self.bounce_range : self.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 && self.damping < 255
var damping_factor = tasmota.scale_uint(self.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
if self.source_animation != nil
self.source_animation.render(self.source_frame, 0)
end
# Apply bounce transformation
var pixel_position = self.current_position / 256 # Convert to pixel units
var offset = pixel_position - self.strip_length / 2 # Offset from center
var i = 0
while i < self.strip_length
var source_pos = i - offset
# Clamp to strip bounds
if source_pos >= 0 && source_pos < self.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 i = 0
while i < self.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
# Global constructor functions
# Create a basic bounce animation
#
# @param source_animation: Animation - Source animation to bounce
# @param bounce_speed: int - Bounce speed (0-255)
# @param damping: int - Damping factor (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return BounceAnimation - A new bounce animation instance
def bounce_basic(source_animation, bounce_speed, damping, strip_length, priority)
return animation.bounce_animation(
source_animation,
bounce_speed,
0, # full strip range
damping,
0, # no gravity
strip_length,
priority,
0, # infinite duration
true, # loop
"bounce_basic"
)
end
# Create a gravity bounce animation
#
# @param source_animation: Animation - Source animation to bounce
# @param bounce_speed: int - Initial bounce speed (0-255)
# @param gravity: int - Gravity strength (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return BounceAnimation - A new bounce animation instance
def bounce_gravity(source_animation, bounce_speed, gravity, strip_length, priority)
return animation.bounce_animation(
source_animation,
bounce_speed,
0, # full strip range
240, # high damping
gravity,
strip_length,
priority,
0, # infinite duration
true, # loop
"bounce_gravity"
)
end
# Create a constrained bounce animation
#
# @param source_animation: Animation - Source animation to bounce
# @param bounce_speed: int - Bounce speed (0-255)
# @param bounce_range: int - Bounce range in pixels
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return BounceAnimation - A new bounce animation instance
def bounce_constrained(source_animation, bounce_speed, bounce_range, strip_length, priority)
return animation.bounce_animation(
source_animation,
bounce_speed,
bounce_range,
250, # high damping
0, # no gravity
strip_length,
priority,
0, # infinite duration
true, # loop
"bounce_constrained"
)
end
return {'bounce_animation': BounceAnimation, 'bounce_basic': bounce_basic, 'bounce_gravity': bounce_gravity, 'bounce_constrained': bounce_constrained}

View File

@ -0,0 +1,203 @@
# Breathe animation effect for Berry Animation Framework
#
# This animation creates a smooth breathing effect that oscillates between a minimum and maximum brightness
# using a more natural breathing curve than a simple sine wave.
# It's useful for creating calming, organic lighting effects.
#@ solidify:BreatheAnimation,weak
class BreatheAnimation : animation.animation
var color # The color to breathe (32-bit ARGB value)
var min_brightness # Minimum brightness level (0-255)
var max_brightness # Maximum brightness level (0-255)
var breathe_period # Time for one complete breathe cycle in milliseconds
var curve_factor # Factor to control the breathing curve shape (1-5, higher = sharper)
var current_brightness # Current brightness level (calculated during update)
# Initialize a new Breathe animation
#
# @param color: int - 32-bit color value in ARGB format (0xAARRGGBB), defaults to white (0xFFFFFFFF) if nil
# @param min_brightness: int - Minimum brightness level (0-255), defaults to 0 if nil
# @param max_brightness: int - Maximum brightness level (0-255), defaults to 255 if nil
# @param breathe_period: int - Time for one complete breathe cycle in milliseconds, defaults to 3000ms if nil
# @param curve_factor: int - Factor to control the breathing curve shape (1-5, higher = sharper), defaults to 2 if nil
# @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 name: string - Optional name for the animation, defaults to "breathe" if nil
def init(color, min_brightness, max_brightness, breathe_period, curve_factor, priority, duration, loop, name)
# Call parent constructor with new signature: (priority, duration, loop, opacity, name)
super(self).init(priority, duration, loop, 255, name != nil ? name : "breathe")
# Set initial values with defaults
self.color = color != nil ? color : 0xFFFFFFFF # Default to white
self.min_brightness = min_brightness != nil ? min_brightness : 0 # Default to 0
self.max_brightness = max_brightness != nil ? max_brightness : 255 # Default to full brightness
self.breathe_period = breathe_period != nil ? breathe_period : 3000 # Default to 3 seconds
self.curve_factor = curve_factor != nil ? curve_factor : 2 # Default to medium curve
self.current_brightness = self.min_brightness # Start at min brightness
# Register parameters with validation
self.register_param("color", {"default": 0xFFFFFFFF})
self.register_param("min_brightness", {"min": 0, "max": 255, "default": 0})
self.register_param("max_brightness", {"min": 0, "max": 255, "default": 255})
self.register_param("breathe_period", {"min": 100, "default": 3000})
self.register_param("curve_factor", {"min": 1, "max": 5, "default": 2})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("min_brightness", self.min_brightness)
self.set_param("max_brightness", self.max_brightness)
self.set_param("breathe_period", self.breathe_period)
self.set_param("curve_factor", self.curve_factor)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
self.color = value
elif name == "min_brightness"
self.min_brightness = value
elif name == "max_brightness"
self.max_brightness = value
elif name == "breathe_period"
self.breathe_period = value
elif name == "curve_factor"
self.curve_factor = 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
# 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.breathe_period, 0, self.breathe_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 breathe_factor = tasmota.sine_int(cycle_position) + 4096 # range is 0..8192
# Apply curve factor to create more natural breathing effect
# Higher curve_factor makes the breathing more pronounced at the peaks
# This creates a pause at the top and bottom of the breath
if self.curve_factor > 1
# Apply power function to create curve
# We use a simple approximation since Berry doesn't have a built-in power function
var factor = self.curve_factor
while factor > 1
breathe_factor = (breathe_factor * breathe_factor) / 8192
factor -= 1
end
end
# Calculate current brightness based on min/max and breathe factor
self.current_brightness = tasmota.scale_uint(breathe_factor, 0, 8192, self.min_brightness, self.max_brightness)
#print(f"DEBUG {self.curve_factor=} {elapsed=} {self.breathe_period=} {cycle_position=} {breathe_factor=} {self.current_brightness=}")
return true
end
# Render the breathing color 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 tasmota.millis())
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Resolve the current color using resolve_value
var current_color = self.resolve_value(self.color, "color", time_ms)
# Fill the entire frame with the resolved color
frame.fill_pixels(current_color)
# Apply current brightness
frame.apply_brightness(self.current_brightness)
return true
end
# Set the color
#
# @param color: int - 32-bit color value in ARGB format (0xAARRGGBB)
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the minimum brightness
#
# @param brightness: int - Minimum brightness level (0-255)
# @return self for method chaining
def set_min_brightness(brightness)
self.set_param("min_brightness", brightness)
return self
end
# Set the maximum brightness
#
# @param brightness: int - Maximum brightness level (0-255)
# @return self for method chaining
def set_max_brightness(brightness)
self.set_param("max_brightness", brightness)
return self
end
# Set the breathe period
#
# @param period: int - Time for one complete breathe cycle in milliseconds
# @return self for method chaining
def set_breathe_period(period)
self.set_param("breathe_period", period)
return self
end
# Set the curve factor
#
# @param factor: int - Factor to control the breathing curve shape (1-5)
# @return self for method chaining
def set_curve_factor(factor)
self.set_param("curve_factor", factor)
return self
end
# Create a breathe animation with a color value
#
# @param color: int - 32-bit color value in ARGB format (0xAARRGGBB)
# @param min_brightness: int - Minimum brightness level (0-255)
# @param max_brightness: int - Maximum brightness level (0-255)
# @param breathe_period: int - Time for one complete breathe cycle in milliseconds
# @param curve_factor: int - Factor to control the breathing curve shape (1-5)
# @param priority: int - Rendering priority (higher = on top)
# @return BreatheAnimation - A new breathe animation instance
static def from_rgb(color, min_brightness, max_brightness, breathe_period, curve_factor, priority)
# Create and return a new breathe animation
return animation.breathe_animation(color, min_brightness, max_brightness, breathe_period, curve_factor, priority)
end
# String representation of the animation
def tostring()
return f"BreatheAnimation(color=0x{self.color :08x}, min_brightness={self.min_brightness}, max_brightness={self.max_brightness}, breathe_period={self.breathe_period}, curve_factor={self.curve_factor}, priority={self.priority}, running={self.is_running})"
end
end
# Alias to simpler 'breathe'
def breathe(color, min_brightness, max_brightness, breathe_period, curve_factor, priority, duration, loop, name)
return animation.breathe_animation(color, min_brightness, max_brightness, breathe_period, curve_factor, priority, duration, loop, name)
end
return {'breathe_animation': BreatheAnimation,
'breathe': breathe}

View File

@ -0,0 +1,337 @@
# 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.
#@ solidify:CometAnimation,weak
class CometAnimation : animation.animation
var color # Color for the comet head (32-bit ARGB value or ValueProvider instance)
var head_position # Current position of the comet head (in 1/256th pixels for smooth movement)
var tail_length # Length of the comet tail in pixels
var speed # Movement speed in 1/256th pixels per second
var direction # Direction of movement (1 = forward, -1 = backward)
var wrap_around # Whether comet wraps around the strip (bool)
var fade_factor # How quickly the tail fades (0-255, where 255 = no fade, 128 = 50% fade)
var strip_length # Length of the LED strip
# Initialize a new Comet animation
#
# @param color: int|ValueProvider - Color for the comet head (32-bit ARGB value or ValueProvider instance), defaults to white (0xFFFFFFFF) if nil
# @param tail_length: int - Length of the comet tail in pixels, defaults to 5 if nil
# @param speed: int - Movement speed in 1/256th pixels per second (256 = 1 pixel/sec, 512 = 2 pixels/sec), defaults to 2560 (10 pixels/sec) if nil
# @param direction: int - Direction of movement (1 = forward, -1 = backward), defaults to 1 if nil
# @param wrap_around: bool - Whether comet wraps around the strip, defaults to true if nil
# @param fade_factor: int - How quickly the tail fades (0-255, where 255 = no fade, 128 = 50% fade), defaults to 179 (~70% fade) if nil
# @param strip_length: int - Length of the LED strip, defaults to 30 if nil
# @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 name: string - Optional name for the animation, defaults to "comet" if nil
def init(color, tail_length, speed, direction, wrap_around, fade_factor, strip_length, priority, duration, loop, name)
# Call parent constructor with new signature: (priority, duration, loop, opacity, name)
super(self).init(priority, duration, loop, 255, name != nil ? name : "comet")
# Set initial values with defaults
self.color = color != nil ? color : 0xFFFFFFFF # Default to white
self.tail_length = tail_length != nil ? tail_length : 5
self.speed = speed != nil ? speed : 2560 # Default: 10 pixels per second (10 * 256)
self.direction = direction != nil ? direction : 1
self.wrap_around = wrap_around != nil ? wrap_around : true
self.fade_factor = fade_factor != nil ? fade_factor : 179 # ~70% fade (179/255 ≈ 0.7)
self.strip_length = strip_length != nil ? strip_length : 30
# Initialize position (in 1/256th pixels)
if self.direction > 0
self.head_position = 0 # Start at beginning for forward movement
else
self.head_position = (self.strip_length - 1) * 256 # Start at end for backward movement
end
# Register parameters with validation
self.register_param("color", {"default": 0xFFFFFFFF})
self.register_param("tail_length", {"min": 1, "max": 50, "default": 5})
self.register_param("speed", {"min": 1, "max": 25600, "default": 2560})
self.register_param("direction", {"enum": [-1, 1], "default": 1})
self.register_param("wrap_around", {"min": 0, "max": 1, "default": 1})
self.register_param("fade_factor", {"min": 0, "max": 255, "default": 179})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("tail_length", self.tail_length)
self.set_param("speed", self.speed)
self.set_param("direction", self.direction)
self.set_param("wrap_around", self.wrap_around ? 1 : 0)
self.set_param("fade_factor", self.fade_factor)
self.set_param("strip_length", self.strip_length)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
self.color = value
elif name == "tail_length"
self.tail_length = value
elif name == "speed"
self.speed = value
elif name == "direction"
self.direction = value
elif name == "wrap_around"
self.wrap_around = value != 0
elif name == "fade_factor"
self.fade_factor = value
elif name == "strip_length"
self.strip_length = 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
# 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 = (self.speed * elapsed * self.direction) / 1000
# Update head position
if self.direction > 0
self.head_position = distance_moved
else
self.head_position = ((self.strip_length - 1) * 256) + distance_moved
end
# Handle wrapping or bouncing (convert to pixel boundaries)
var strip_length_subpixels = self.strip_length * 256
if self.wrap_around
# 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 = (self.strip_length - 1) * 256
self.direction = -self.direction
self.set_param("direction", self.direction)
elif self.head_position < 0
self.head_position = 0
self.direction = -self.direction
self.set_param("direction", self.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 - Optional current time in milliseconds (defaults to tasmota.millis())
# @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
# Resolve all parameters using resolve_value
var current_color = self.resolve_value(self.color, "color", time_ms)
var tail_length = self.resolve_value(self.tail_length, "tail_length", time_ms)
var direction = self.resolve_value(self.direction, "direction", time_ms)
var wrap_around = self.resolve_value(self.wrap_around, "wrap_around", time_ms)
var fade_factor = self.resolve_value(self.fade_factor, "fade_factor", time_ms)
var strip_length = self.resolve_value(self.strip_length, "strip_length", time_ms)
# 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
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
# Set the color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the tail length
#
# @param length: int - Length of the comet tail in pixels
# @return self for method chaining
def set_tail_length(length)
self.set_param("tail_length", length)
return self
end
# Set the movement speed
#
# @param speed: int - Movement speed in 1/256th pixels per second
# @return self for method chaining
def set_speed(speed)
self.set_param("speed", speed)
return self
end
# Set the movement direction
#
# @param direction: int - Direction of movement (1 = forward, -1 = backward)
# @return self for method chaining
def set_direction(direction)
self.set_param("direction", direction)
return self
end
# Set whether the comet wraps around
#
# @param wrap: bool - Whether comet wraps around the strip
# @return self for method chaining
def set_wrap_around(wrap)
self.set_param("wrap_around", int(wrap))
return self
end
# Set the fade factor
#
# @param factor: int - How quickly the tail fades (0-255, where 255 = no fade, 128 = 50% fade)
# @return self for method chaining
def set_fade_factor(factor)
self.set_param("fade_factor", factor)
return self
end
# Set the strip length
#
# @param length: int - Length of the LED strip
# @return self for method chaining
def set_strip_length(length)
self.set_param("strip_length", length)
return self
end
# Factory method to create a comet animation with a solid color
#
# @param color: int - 32-bit color value in ARGB format (0xAARRGGBB)
# @param tail_length: int - Length of the comet tail in pixels
# @param speed: int - Movement speed in 1/256th pixels per second
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return CometAnimation - A new comet animation instance
static def solid(color, tail_length, speed, strip_length, priority)
return animation.comet_animation(color, tail_length, speed, 1, true, 179, strip_length, priority, 0, true, "comet_solid")
end
# Factory method to create a comet animation with a color cycle provider
#
# @param palette: list - List of colors to cycle through (32-bit ARGB values)
# @param cycle_period: int - Time for one complete cycle in milliseconds
# @param tail_length: int - Length of the comet tail in pixels
# @param speed: int - Movement speed in 1/256th pixels per second
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return CometAnimation - A new comet animation instance
static def color_cycle(palette, cycle_period, tail_length, speed, strip_length, priority)
var provider = animation.color_cycle_color_provider(
palette != nil ? palette : [0xFF0000FF, 0xFF00FF00, 0xFFFF0000],
cycle_period != nil ? cycle_period : 5000,
1 # sine transition
)
return animation.comet_animation(provider, tail_length, speed, 1, true, 179, strip_length, priority, 0, true, "comet_color_cycle")
end
# Factory method to create a comet animation with a rich palette provider
#
# @param palette_bytes: bytes - Compact palette in bytes format
# @param cycle_period: int - Time for one complete cycle in milliseconds
# @param tail_length: int - Length of the comet tail in pixels
# @param speed: int - Movement speed in 1/256th pixels per second
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return CometAnimation - A new comet animation instance
static def rich_palette(palette_bytes, cycle_period, tail_length, speed, strip_length, priority)
var provider = animation.rich_palette_color_provider(
palette_bytes,
cycle_period != nil ? cycle_period : 5000,
1, # sine transition
255 # full brightness
)
return animation.comet_animation(provider, tail_length, speed, 1, true, 179, strip_length, priority, 0, true, "comet_rich_palette")
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:.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,258 @@
# 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
var color # The pulse color (32-bit ARGB value or ValueProvider instance)
var back_color # The background color (32-bit ARGB value)
var pos # Position of the first pulse start (in pixels)
var pulse_size # Number of pixels for each pulse width
var low_size # Number of pixels between pulses (low period)
var nb_pulse # Number of pulses (-1 for infinite)
# Initialize a new Crenel Position animation
#
# @param color: int|ValueProvider - 32-bit pulse color value in ARGB format (0xAARRGGBB) or a ValueProvider instance, defaults to white (0xFFFFFFFF) if nil
# @param pos: int|ValueProvider - Position of the first pulse start (in pixels) or a ValueProvider instance, defaults to 0 if nil
# @param pulse_size: int|ValueProvider - Number of pixels for each pulse width or a ValueProvider instance, defaults to 1 if nil
# @param low_size: int|ValueProvider - Number of pixels between pulses or a ValueProvider instance, defaults to 3 if nil
# @param nb_pulse: int|ValueProvider - Number of pulses (-1 for infinite) or a ValueProvider instance, defaults to -1 (infinite) if nil
# @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 name: string - Optional name for the animation, defaults to "crenel_position" if nil
def init(color, pos, pulse_size, low_size, nb_pulse, priority, duration, loop, name)
# Call parent constructor with new signature: (priority, duration, loop, opacity, name)
super(self).init(priority, duration, loop, 255, name != nil ? name : "crenel_position")
# Set initial values with defaults
self.color = color != nil ? color : 0xFFFFFFFF # Default to white
self.back_color = 0xFF000000 # Default to transparent
self.pulse_size = pulse_size != nil ? pulse_size : 1 # Default to 1 pixel
self.low_size = low_size != nil ? low_size : 3 # Default to 3 pixels
self.nb_pulse = nb_pulse != nil ? nb_pulse : -1 # Default to infinite
self.pos = pos != nil ? pos : 0 # Default position at start
# Ensure non-negative values for sizes (only for static values)
# Skip validation for value providers since they can't be validated at construction time
if type(self.pulse_size) == "int" && self.pulse_size < 0
self.pulse_size = 0
end
if type(self.low_size) == "int" && self.low_size < 0
self.low_size = 0
end
# Register parameters with validation
self.register_param("color", {"default": 0xFFFFFFFF}) # Can be int or ValueProvider
self.register_param("back_color", {"default": 0xFF000000})
self.register_param("pos", {"default": 0})
self.register_param("pulse_size", {"min": 0, "default": 1})
self.register_param("low_size", {"min": 0, "default": 3})
self.register_param("nb_pulse", {"default": -1})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("back_color", self.back_color)
self.set_param("pos", self.pos)
self.set_param("pulse_size", self.pulse_size)
self.set_param("low_size", self.low_size)
self.set_param("nb_pulse", self.nb_pulse)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
self.color = value
elif name == "back_color"
self.back_color = value
elif name == "pos"
self.pos = value
elif name == "pulse_size"
self.pulse_size = value
# Only validate static values, not value providers
if type(self.pulse_size) == "int" && self.pulse_size < 0
self.pulse_size = 0
end
elif name == "low_size"
self.low_size = value
# Only validate static values, not value providers
if type(self.low_size) == "int" && self.low_size < 0
self.low_size = 0
end
elif name == "nb_pulse"
self.nb_pulse = 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
return super(self).update(time_ms)
end
# 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 tasmota.millis())
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
var pixel_size = frame.width
# Resolve all parameters - handle both static values and value providers
var back_color = self.resolve_value(self.back_color, "back_color", time_ms)
var pos = self.resolve_value(self.pos, "pos", time_ms)
var pulse_size = self.resolve_value(self.pulse_size, "pulse_size", time_ms)
var low_size = self.resolve_value(self.low_size, "low_size", time_ms)
var nb_pulse = self.resolve_value(self.nb_pulse, "nb_pulse", time_ms)
var color = self.resolve_value(self.color, "color", time_ms)
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
# Set the pulse color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the background color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_back_color(color)
self.set_param("back_color", color)
return self
end
# Set the pulse position
#
# @param pos: int|ValueProvider - Position of the first pulse start (in pixels) or a ValueProvider instance
# @return self for method chaining
def set_pos(pos)
self.set_param("pos", pos)
return self
end
# Set the pulse size
#
# @param pulse_size: int|ValueProvider - Number of pixels for each pulse width or a ValueProvider instance
# @return self for method chaining
def set_pulse_size(pulse_size)
# Only validate static values, not value providers
if type(pulse_size) == "int" && pulse_size < 0
pulse_size = 0
end
self.set_param("pulse_size", pulse_size)
return self
end
# Set the low size (spacing between pulses)
#
# @param low_size: int|ValueProvider - Number of pixels between pulses or a ValueProvider instance
# @return self for method chaining
def set_low_size(low_size)
# Only validate static values, not value providers
if type(low_size) == "int" && low_size < 0
low_size = 0
end
self.set_param("low_size", low_size)
return self
end
# Set the number of pulses
#
# @param nb_pulse: int|ValueProvider - Number of pulses (-1 for infinite) or a ValueProvider instance
# @return self for method chaining
def set_nb_pulse(nb_pulse)
self.set_param("nb_pulse", nb_pulse)
return self
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"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,166 @@
# FilledAnimation for Berry Animation Framework
#
# This animation fills the frame with colors from any color provider.
# It serves as a unified replacement for multiple specific animation effects.
#@ solidify:FilledAnimation,weak
class FilledAnimation : animation.animation
var color # The color for the fill (32-bit ARGB value or ValueProvider instance)
# Initialize a new Filled animation
#
# @param color: int|ValueProvider - Color for the fill (32-bit ARGB value or ValueProvider instance), defaults to white (0xFFFFFFFF) if nil
# @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 name: string - Optional name for the animation
def init(color, priority, duration, loop, name)
# Call parent constructor
super(self).init(priority, duration, loop, 255, name != nil ? name : "filled")
# Set initial values with defaults
self.color = color != nil ? color : 0xFFFFFFFF # Default to white
# Register the color parameter
self.register_param("color", {"default": 0xFFFFFFFF})
self.set_param("color", self.color)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
self.color = 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
return super(self).update(time_ms)
end
# Render the current color 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 tasmota.millis())
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Resolve the current color using resolve_value
var current_color = self.resolve_value(self.color, "color", time_ms)
# Fill the entire frame with the current color
frame.fill_pixels(current_color)
# Resolve and apply opacity if not full
var current_opacity = self.resolve_value(self.opacity, "opacity", time_ms)
if current_opacity < 255
frame.apply_opacity(current_opacity)
end
return true
end
# Set the color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Get a color for a specific value (0-100)
#
# @param value: int/float - Value to map to a color (0-100)
# @return int - Color in ARGB format
def get_color_for_value(value)
var current_time = tasmota.millis()
var resolved_color = self.resolve_value(self.color, "color", current_time)
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(self.color) && self.color.get_color_for_value != nil
return self.color.get_color_for_value(value, current_time)
end
return resolved_color
end
# Force conversion of the instance to the current color
#
# @return int - Color in ARGB format
def toint()
return self.resolve_value(self.color, "color", tasmota.millis())
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"FilledAnimation(color={color_str}, priority={self.priority}, running={self.is_running})"
end
end
# Factory method to create a filled animation with a color cycle provider
#
# @param palette: list - List of colors to cycle through (32-bit ARGB values), defaults to [red, green, blue] if nil
# @param cycle_period: int - Time for one complete cycle in milliseconds, defaults to 5000ms if nil
# @param transition_type: int - Type of transition (0 = linear, 1 = sine), defaults to 1 (sine) if nil
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
# @return FilledAnimation - A new filled animation instance
def color_cycle(palette, cycle_period, transition_type, priority)
var provider = animation.color_cycle_color_provider(
palette != nil ? palette : [0xFF0000FF, 0xFF00FF00, 0xFFFF0000],
cycle_period != nil ? cycle_period : 5000,
transition_type != nil ? transition_type : 1
)
return animation.filled_animation(provider, priority, 0, false, "color_cycle")
end
# Factory method to create a filled animation with a rich palette provider
#
# @param palette_bytes: bytes - Compact palette in bytes format, required (no default)
# @param cycle_period: int - Time for one complete cycle in milliseconds, defaults to 5000ms if nil
# @param transition_type: int - Type of transition (0 = linear, 1 = sine), defaults to 1 (sine) if nil
# @param brightness: int - Brightness level (0-255), defaults to 255 if nil
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
# @return FilledAnimation - A new filled animation instance
def rich_palette(palette_bytes, cycle_period, transition_type, brightness, priority)
var provider = animation.rich_palette_color_provider(
palette_bytes,
cycle_period != nil ? cycle_period : 5000,
transition_type != nil ? transition_type : 1,
brightness != nil ? brightness : 255
)
return animation.filled_animation(provider, priority, 0, false, "rich_palette")
end
# Factory method to create a filled animation with a composite color provider
#
# @param providers: list - List of color providers, required (no default)
# @param blend_mode: int - How to blend colors (0 = overlay, 1 = add, 2 = multiply), defaults to 0 (overlay) if nil
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
# @return FilledAnimation - A new filled animation instance
def composite(providers, blend_mode, priority)
var provider = animation.composite_color_provider(
providers,
blend_mode != nil ? blend_mode : 0
)
return animation.filled_animation(provider, priority, 0, false, "composite")
end
return {'color_cycle_animation': color_cycle,
'rich_palette_animation': rich_palette,
'composite_animation': composite,
'filled_animation': FilledAnimation}

View File

@ -0,0 +1,409 @@
# 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
var color # Color for fire colors (32-bit ARGB value or ValueProvider instance)
var intensity # Base fire intensity (0-255)
var flicker_speed # Speed of flickering in Hz (flickers per second)
var flicker_amount # Amount of flicker (0-255, where 0 = no flicker, 255 = max flicker)
var heat_map # Array storing heat values for each pixel (0-255)
var cooling_rate # How quickly heat dissipates (0-255)
var sparking_rate # How often new sparks are created (0-255)
var strip_length # Length of the LED strip
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
# Initialize a new Fire animation
#
# @param color: int|ValueProvider - Color for fire colors (32-bit ARGB value or ValueProvider instance), defaults to fire palette if nil
# @param intensity: int - Base fire intensity (0-255), defaults to 180 if nil
# @param flicker_speed: int - Speed of flickering in Hz (1-20), defaults to 8 if nil
# @param flicker_amount: int - Amount of flicker (0-255), defaults to 100 if nil
# @param cooling_rate: int - How quickly heat dissipates (0-255, higher = faster cooling), defaults to 55 if nil
# @param sparking_rate: int - How often new sparks are created (0-255, higher = more sparks), defaults to 120 if nil
# @param strip_length: int - Length of the LED strip, defaults to 30 if nil
# @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 name: string - Optional name for the animation, defaults to "fire" if nil
def init(color, intensity, flicker_speed, flicker_amount, cooling_rate, sparking_rate, strip_length, priority, duration, loop, name)
# Call parent constructor with new signature: (priority, duration, loop, opacity, name)
super(self).init(priority, duration, loop, 255, name != nil ? name : "fire")
# Set initial values with defaults
if color == nil
# Default to fire palette if no color provided
var fire_provider = animation.rich_palette_color_provider(
animation.PALETTE_FIRE,
5000, # cycle period (not used for value-based colors)
1, # sine transition
255 # full brightness
)
# Set range for value-based color mapping (heat values 0-255)
fire_provider.set_range(0, 255)
self.color = fire_provider
else
self.color = color
end
# Set initial values with defaults
self.intensity = intensity != nil ? intensity : 180
self.flicker_speed = flicker_speed != nil ? flicker_speed : 8 # 8 Hz flicker
self.flicker_amount = flicker_amount != nil ? flicker_amount : 100
self.cooling_rate = cooling_rate != nil ? cooling_rate : 55
self.sparking_rate = sparking_rate != nil ? sparking_rate : 120
self.strip_length = strip_length != nil ? strip_length : 30
# Initialize heat map and color arrays
self.heat_map = []
self.current_colors = []
self.heat_map.resize(self.strip_length)
self.current_colors.resize(self.strip_length)
# Initialize all pixels to zero heat
var i = 0
while i < self.strip_length
self.heat_map[i] = 0
self.current_colors[i] = 0xFF000000 # Black with full alpha
i += 1
end
# Initialize timing
self.last_update = 0
# Initialize random seed
var current_time = tasmota.millis()
self.random_seed = current_time % 65536
# Register parameters with validation
self.register_param("color", {"default": nil})
self.register_param("intensity", {"min": 0, "max": 255, "default": 180})
self.register_param("flicker_speed", {"min": 1, "max": 20, "default": 8})
self.register_param("flicker_amount", {"min": 0, "max": 255, "default": 100})
self.register_param("cooling_rate", {"min": 0, "max": 255, "default": 55})
self.register_param("sparking_rate", {"min": 0, "max": 255, "default": 120})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("intensity", self.intensity)
self.set_param("flicker_speed", self.flicker_speed)
self.set_param("flicker_amount", self.flicker_amount)
self.set_param("cooling_rate", self.cooling_rate)
self.set_param("sparking_rate", self.sparking_rate)
self.set_param("strip_length", self.strip_length)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
if value == nil
# Reset to default fire palette
var fire_provider = animation.rich_palette_color_provider(
animation.PALETTE_FIRE,
5000,
1,
255
)
fire_provider.set_range(0, 255)
self.color = fire_provider
else
self.color = value
end
elif name == "intensity"
self.intensity = value
elif name == "flicker_speed"
self.flicker_speed = value
elif name == "flicker_amount"
self.flicker_amount = value
elif name == "cooling_rate"
self.cooling_rate = value
elif name == "sparking_rate"
self.sparking_rate = value
elif name == "strip_length"
if value != self.strip_length
self.strip_length = value
# Resize arrays
self.heat_map.resize(self.strip_length)
self.current_colors.resize(self.strip_length)
# Initialize new pixels to zero heat
var i = 0
while i < self.strip_length
if self.heat_map[i] == nil
self.heat_map[i] = 0
end
if self.current_colors[i] == nil
self.current_colors[i] = 0xFF000000
end
i += 1
end
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
# Check if it's time to update the fire simulation
# Update frequency is based on flicker_speed (Hz)
var update_interval = 1000 / self.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)
# Step 1: Cool down every pixel a little
var i = 0
while i < self.strip_length
var cooldown = self._random_range(tasmota.scale_uint(self.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 self.strip_length >= 3
var k = self.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) < self.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 < self.strip_length
self.heat_map[spark_pos] = spark_heat
end
end
# Step 4: Convert heat to colors
i = 0
while i < self.strip_length
var heat = self.heat_map[i]
# Apply base intensity scaling
heat = tasmota.scale_uint(heat, 0, 255, 0, self.intensity)
# Add flicker effect
if self.flicker_amount > 0
var flicker = self._random_range(self.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
# Resolve the color using resolve_value
var resolved_color = self.resolve_value(self.color, "color", time_ms)
# If the color is a provider that supports get_color_for_value, use it
if animation.is_color_provider(self.color) && self.color.get_color_for_value != nil
# Use value-based color mapping for heat
color = self.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 tasmota.millis())
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Render each pixel with its current color
var i = 0
while i < self.strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# Set the color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the base fire intensity
#
# @param intensity: int - Base fire intensity (0-255)
# @return self for method chaining
def set_intensity(intensity)
self.set_param("intensity", intensity)
return self
end
# Set the flicker speed
#
# @param speed: int - Speed of flickering in Hz (1-20)
# @return self for method chaining
def set_flicker_speed(speed)
self.set_param("flicker_speed", speed)
return self
end
# Set the flicker amount
#
# @param amount: int - Amount of flicker (0-255)
# @return self for method chaining
def set_flicker_amount(amount)
self.set_param("flicker_amount", amount)
return self
end
# Set the cooling rate
#
# @param rate: int - How quickly heat dissipates (0-255)
# @return self for method chaining
def set_cooling_rate(rate)
self.set_param("cooling_rate", rate)
return self
end
# Set the sparking rate
#
# @param rate: int - How often new sparks are created (0-255)
# @return self for method chaining
def set_sparking_rate(rate)
self.set_param("sparking_rate", rate)
return self
end
# Set the strip length
#
# @param length: int - Length of the LED strip
# @return self for method chaining
def set_strip_length(length)
self.set_param("strip_length", length)
return self
end
# Factory method to create a fire animation with default fire colors
#
# @param intensity: int - Base fire intensity (0-255)
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return FireAnimation - A new fire animation instance
static def classic(intensity, strip_length, priority)
return animation.fire_animation(nil, intensity, 8, 100, 55, 120, strip_length, priority, 0, true, "fire_classic")
end
# Factory method to create a fire animation with custom colors
#
# @param color: int - Single color for the fire effect
# @param intensity: int - Base fire intensity (0-255)
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return FireAnimation - A new fire animation instance
static def solid(color, intensity, strip_length, priority)
return animation.fire_animation(color, intensity, 8, 100, 55, 120, strip_length, priority, 0, true, "fire_solid")
end
# Factory method to create a fire animation with a custom palette
#
# @param palette_bytes: bytes - Compact palette in bytes format
# @param intensity: int - Base fire intensity (0-255)
# @param strip_length: int - Length of the LED strip
# @param priority: int - Rendering priority (higher = on top)
# @return FireAnimation - A new fire animation instance
static def palette(palette_bytes, intensity, strip_length, priority)
var provider = animation.rich_palette_color_provider(
palette_bytes,
5000, # cycle period (not used for value-based colors)
1, # sine transition
255 # full brightness
)
provider.set_range(0, 255) # Map heat values 0-255 to palette
return animation.fire_animation(provider, intensity, 8, 100, 55, 120, strip_length, priority, 0, true, "fire_palette")
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,424 @@
# 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
var color # Color for gradient colors (32-bit ARGB value or ValueProvider instance)
var gradient_type # Type: 0=linear, 1=radial
var direction # Direction for linear gradients (0-255, 0=left-to-right, 128=right-to-left)
var center_pos # Center position for radial gradients (0-255)
var spread # Gradient spread/width (0-255)
var movement_speed # Speed of gradient movement (0-255, 0=static)
var strip_length # Length of the LED strip
var current_colors # Array of current colors for each pixel
var phase_offset # Current phase offset for movement
# Initialize a new Gradient animation
#
# @param color: int|ValueProvider - Color for gradient colors (32-bit ARGB value or ValueProvider instance), defaults to rainbow if nil
# @param gradient_type: int - Type (0=linear, 1=radial), defaults to 0 if nil
# @param direction: int - Direction for linear (0-255), defaults to 0 if nil
# @param center_pos: int - Center position for radial (0-255), defaults to 128 if nil
# @param spread: int - Gradient spread (0-255), defaults to 255 if nil
# @param movement_speed: int - Movement speed (0-255), defaults to 0 if nil
# @param strip_length: int - Length of LED strip, defaults to 30 if nil
# @param priority: int - Rendering priority, defaults to 10 if nil
# @param duration: int - Duration in ms, defaults to 0 (infinite) if nil
# @param loop: bool - Whether to loop, defaults to true if nil
# @param name: string - Animation name, defaults to "gradient" if nil
def init(color, gradient_type, direction, center_pos, spread, movement_speed, strip_length, priority, duration, loop, name)
# Call parent constructor
super(self).init(priority, duration, loop != nil ? loop : true, 255, name != nil ? name : "gradient")
# Set initial values with defaults
if color == nil
# Default rainbow gradient
var rainbow_provider = animation.rich_palette_color_provider(
animation.PALETTE_RAINBOW,
5000, # cycle period
1, # sine transition
255 # full brightness
)
rainbow_provider.set_range(0, 255)
self.color = rainbow_provider
elif type(color) == "int"
# Single color - create a simple gradient 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((color >> 16) & 0xFF, 1) # R
palette.add((color >> 8) & 0xFF, 1) # G
palette.add(color & 0xFF, 1) # B
var gradient_provider = animation.rich_palette_color_provider(
palette, 5000, 1, 255
)
gradient_provider.set_range(0, 255)
self.color = gradient_provider
else
# Assume it's already a color provider
self.color = color
end
# Set parameters with defaults
self.gradient_type = gradient_type != nil ? gradient_type : 0
self.direction = direction != nil ? direction : 0
self.center_pos = center_pos != nil ? center_pos : 128
self.spread = spread != nil ? spread : 255
self.movement_speed = movement_speed != nil ? movement_speed : 0
self.strip_length = strip_length != nil ? strip_length : 30
# Initialize arrays and state
self.current_colors = []
self.current_colors.resize(self.strip_length)
self.phase_offset = 0
# Initialize colors to black
var i = 0
while i < self.strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
# Register parameters
self.register_param("color", {"default": nil})
self.register_param("gradient_type", {"min": 0, "max": 1, "default": 0})
self.register_param("direction", {"min": 0, "max": 255, "default": 0})
self.register_param("center_pos", {"min": 0, "max": 255, "default": 128})
self.register_param("spread", {"min": 1, "max": 255, "default": 255})
self.register_param("movement_speed", {"min": 0, "max": 255, "default": 0})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("gradient_type", self.gradient_type)
self.set_param("direction", self.direction)
self.set_param("center_pos", self.center_pos)
self.set_param("spread", self.spread)
self.set_param("movement_speed", self.movement_speed)
self.set_param("strip_length", self.strip_length)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
if value == nil
# Reset to default rainbow gradient
var rainbow_provider = animation.rich_palette_color_provider(
animation.PALETTE_RAINBOW,
5000,
1,
255
)
rainbow_provider.set_range(0, 255)
self.color = rainbow_provider
else
self.color = value
end
elif name == "gradient_type"
self.gradient_type = value
elif name == "direction"
self.direction = value
elif name == "center_pos"
self.center_pos = value
elif name == "spread"
self.spread = value
elif name == "movement_speed"
self.movement_speed = value
elif name == "strip_length"
if value != self.strip_length
self.strip_length = value
self.current_colors.resize(self.strip_length)
var i = 0
while i < self.strip_length
if self.current_colors[i] == nil
self.current_colors[i] = 0xFF000000
end
i += 1
end
end
end
end
# Update animation state
def update(time_ms)
if !super(self).update(time_ms)
return false
end
# Update movement phase if movement is enabled
if self.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(self.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)
var i = 0
while i < self.strip_length
var gradient_pos = 0
if self.gradient_type == 0
# Linear gradient
gradient_pos = self._calculate_linear_position(i)
else
# Radial gradient
gradient_pos = self._calculate_radial_position(i)
end
# Apply movement offset
gradient_pos = (gradient_pos + self.phase_offset) % 256
# 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(self.color) && self.color.get_color_for_value != nil
color = self.color.get_color_for_value(gradient_pos, 0)
else
# Use resolve_value with position influence
color = self.resolve_value(self.color, "color", time_ms + gradient_pos * 10)
end
self.current_colors[i] = color
i += 1
end
end
# Calculate position for linear gradient
def _calculate_linear_position(pixel)
var strip_pos = tasmota.scale_uint(pixel, 0, self.strip_length - 1, 0, 255)
# Apply direction (0=left-to-right, 128=center-out, 255=right-to-left)
if self.direction <= 128
# Forward direction with varying start point
var start_offset = tasmota.scale_uint(self.direction, 0, 128, 0, 128)
strip_pos = (strip_pos + start_offset) % 256
else
# Reverse direction
var reverse_amount = tasmota.scale_uint(self.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, self.spread)
return strip_pos
end
# Calculate position for radial gradient
def _calculate_radial_position(pixel)
var strip_pos = tasmota.scale_uint(pixel, 0, self.strip_length - 1, 0, 255)
var center = self.center_pos
# 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, self.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 i = 0
while i < self.strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# Set the color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the gradient type
#
# @param gradient_type: int - Gradient type (0=linear, 1=radial)
# @return self for method chaining
def set_gradient_type(gradient_type)
self.set_param("gradient_type", gradient_type)
return self
end
# Set the direction
#
# @param direction: int - Direction for linear gradients (0-255)
# @return self for method chaining
def set_direction(direction)
self.set_param("direction", direction)
return self
end
# Set the center position
#
# @param pos: int - Center position for radial gradients (0-255)
# @return self for method chaining
def set_center_pos(pos)
self.set_param("center_pos", pos)
return self
end
# Set the spread
#
# @param spread: int - Gradient spread/width (0-255)
# @return self for method chaining
def set_spread(spread)
self.set_param("spread", spread)
return self
end
# Set the movement speed
#
# @param speed: int - Speed of gradient movement (0-255)
# @return self for method chaining
def set_movement_speed(speed)
self.set_param("movement_speed", speed)
return self
end
# Set the strip length
#
# @param length: int - Length of the LED strip
# @return self for method chaining
def set_strip_length(length)
self.set_param("strip_length", length)
return self
end
# String representation
def tostring()
var type_str = self.gradient_type == 0 ? "linear" : "radial"
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"GradientAnimation({type_str}, color={color_str}, movement={self.movement_speed}, priority={self.priority}, running={self.is_running})"
end
end
# Global constructor functions (following pattern_animation.be pattern)
# Create a rainbow linear gradient
#
# @param movement_speed: int - Speed of gradient movement (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return GradientAnimation - A new gradient animation instance
def gradient_rainbow_linear(movement_speed, strip_length, priority)
return animation.gradient_animation(
nil, # default rainbow
0, # linear
0, # left-to-right
128, # center (unused for linear)
255, # full spread
movement_speed,
strip_length,
priority,
0, # infinite duration
true, # loop
"rainbow_linear"
)
end
# Create a rainbow radial gradient
#
# @param center_pos: int - Center position (0-255)
# @param movement_speed: int - Speed of gradient movement (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return GradientAnimation - A new gradient animation instance
def gradient_rainbow_radial(center_pos, movement_speed, strip_length, priority)
return animation.gradient_animation(
nil, # default rainbow
1, # radial
0, # direction (unused for radial)
center_pos,
255, # full spread
movement_speed,
strip_length,
priority,
0, # infinite duration
true, # loop
"rainbow_radial"
)
end
# Create a two-color linear gradient
#
# @param color1: int - First color (32-bit ARGB)
# @param color2: int - Second color (32-bit ARGB)
# @param movement_speed: int - Speed of gradient movement (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return GradientAnimation - A new gradient animation instance
def gradient_two_color_linear(color1, color2, movement_speed, strip_length, priority)
# Create a simple two-color palette
var palette = bytes()
palette.add(0x00, 1) # Position 0
palette.add((color1 >> 16) & 0xFF, 1) # R
palette.add((color1 >> 8) & 0xFF, 1) # G
palette.add(color1 & 0xFF, 1) # B
palette.add(0xFF, 1) # Position 255
palette.add((color2 >> 16) & 0xFF, 1) # R
palette.add((color2 >> 8) & 0xFF, 1) # G
palette.add(color2 & 0xFF, 1) # B
var provider = animation.rich_palette_color_provider(palette, 5000, 1, 255)
provider.set_range(0, 255)
return animation.gradient_animation(
provider,
0, # linear
0, # left-to-right
128, # center (unused)
255, # full spread
movement_speed,
strip_length,
priority,
0, # infinite duration
true, # loop
"two_color_linear"
)
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,398 @@
# 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
var source_animation # Source animation to jitter
var jitter_intensity # Jitter intensity (0-255)
var jitter_frequency # Jitter frequency in Hz (0-255)
var jitter_type # Jitter type: 0=position, 1=color, 2=brightness, 3=all
var position_range # Position jitter range in pixels (0-255)
var color_range # Color jitter range (0-255)
var brightness_range # Brightness jitter range (0-255)
var strip_length # Length of the LED strip
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
# Initialize a new Jitter animation
#
# @param source_animation: Animation - Source animation to jitter
# @param jitter_intensity: int - Jitter intensity (0-255), defaults to 100 if nil
# @param jitter_frequency: int - Jitter frequency (0-255), defaults to 60 if nil
# @param jitter_type: int - Jitter type (0-3), defaults to 0 if nil
# @param position_range: int - Position jitter range (0-255), defaults to 50 if nil
# @param color_range: int - Color jitter range (0-255), defaults to 30 if nil
# @param brightness_range: int - Brightness jitter range (0-255), defaults to 40 if nil
# @param strip_length: int - Length of LED strip, defaults to 30 if nil
# @param priority: int - Rendering priority, defaults to 10 if nil
# @param duration: int - Duration in ms, defaults to 0 (infinite) if nil
# @param loop: bool - Whether to loop, defaults to true if nil
# @param name: string - Animation name, defaults to "jitter" if nil
def init(source_animation, jitter_intensity, jitter_frequency, jitter_type, position_range, color_range, brightness_range, strip_length, priority, duration, loop, name)
# Call parent constructor
super(self).init(priority, duration, loop != nil ? loop : true, 255, name != nil ? name : "jitter")
# Set parameters with defaults
self.source_animation = source_animation
self.jitter_intensity = jitter_intensity != nil ? jitter_intensity : 100
self.jitter_frequency = jitter_frequency != nil ? jitter_frequency : 60
self.jitter_type = jitter_type != nil ? jitter_type : 0
self.position_range = position_range != nil ? position_range : 50
self.color_range = color_range != nil ? color_range : 30
self.brightness_range = brightness_range != nil ? brightness_range : 40
self.strip_length = strip_length != nil ? strip_length : 30
# Initialize random seed
var current_time = tasmota.millis()
self.random_seed = current_time % 65536
# Initialize state
self.last_jitter_time = 0
self.jitter_offsets = []
self.jitter_offsets.resize(self.strip_length)
self.source_frame = animation.frame_buffer(self.strip_length)
self.current_colors = []
self.current_colors.resize(self.strip_length)
# Initialize arrays
var i = 0
while i < self.strip_length
self.jitter_offsets[i] = 0
self.current_colors[i] = 0xFF000000
i += 1
end
# Register parameters
self.register_param("jitter_intensity", {"min": 0, "max": 255, "default": 100})
self.register_param("jitter_frequency", {"min": 0, "max": 255, "default": 60})
self.register_param("jitter_type", {"min": 0, "max": 3, "default": 0})
self.register_param("position_range", {"min": 0, "max": 255, "default": 50})
self.register_param("color_range", {"min": 0, "max": 255, "default": 30})
self.register_param("brightness_range", {"min": 0, "max": 255, "default": 40})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("jitter_intensity", self.jitter_intensity)
self.set_param("jitter_frequency", self.jitter_frequency)
self.set_param("jitter_type", self.jitter_type)
self.set_param("position_range", self.position_range)
self.set_param("color_range", self.color_range)
self.set_param("brightness_range", self.brightness_range)
self.set_param("strip_length", self.strip_length)
end
# Handle parameter changes
def on_param_changed(name, value)
if name == "jitter_intensity"
self.jitter_intensity = value
elif name == "jitter_frequency"
self.jitter_frequency = value
elif name == "jitter_type"
self.jitter_type = value
elif name == "position_range"
self.position_range = value
elif name == "color_range"
self.color_range = value
elif name == "brightness_range"
self.brightness_range = value
elif name == "strip_length"
self.strip_length = value
self.jitter_offsets.resize(value)
self.current_colors.resize(value)
self.source_frame = animation.frame_buffer(value)
var i = 0
while i < value
if self.jitter_offsets[i] == nil
self.jitter_offsets[i] = 0
end
if self.current_colors[i] == nil
self.current_colors[i] = 0xFF000000
end
i += 1
end
end
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)
if !super(self).update(time_ms)
return false
end
# Update jitter at specified frequency
if self.jitter_frequency > 0
# Frequency: 0-255 maps to 0-30 Hz
var hz = tasmota.scale_uint(self.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 self.source_animation != nil
if !self.source_animation.is_running
self.source_animation.start(self.start_time)
end
self.source_animation.update(time_ms)
end
# Calculate jittered colors
self._calculate_jitter()
return true
end
# Update jitter offsets
def _update_jitter()
var i = 0
while i < self.strip_length
# Generate new random offset based on intensity
var max_offset = tasmota.scale_uint(self.jitter_intensity, 0, 255, 0, 10)
self.jitter_offsets[i] = self._random_range(max_offset)
i += 1
end
end
# Calculate jittered colors for all pixels
def _calculate_jitter()
# Clear source frame
self.source_frame.clear()
# Render source animation to frame
if self.source_animation != nil
self.source_animation.render(self.source_frame, 0)
end
# Apply jitter transformation
var i = 0
while i < self.strip_length
var base_color = 0xFF000000
if self.jitter_type == 0 || self.jitter_type == 3
# Position jitter
var jitter_pixels = tasmota.scale_uint(self.jitter_offsets[i], -10, 10, -self.position_range / 10, self.position_range / 10)
var source_pos = i + jitter_pixels
# Clamp to strip bounds
if source_pos >= 0 && source_pos < self.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 (self.jitter_type == 1 || self.jitter_type == 2 || self.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)
# Extract ARGB components
var a = (color >> 24) & 0xFF
var r = (color >> 16) & 0xFF
var g = (color >> 8) & 0xFF
var b = color & 0xFF
if self.jitter_type == 1 || self.jitter_type == 3
# Color jitter - add random values to RGB
var color_jitter = tasmota.scale_uint(self.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 self.jitter_type == 2 || self.jitter_type == 3
# Brightness jitter - scale all RGB components
var brightness_jitter = tasmota.scale_uint(self.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 !self.is_running || frame == nil
return false
end
var i = 0
while i < self.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 type_name = type_names[self.jitter_type] != nil ? type_names[self.jitter_type] : "unknown"
return f"JitterAnimation({type_name}, intensity={self.jitter_intensity}, frequency={self.jitter_frequency}, priority={self.priority}, running={self.is_running})"
end
end
# Global constructor functions
# Create a position jitter animation
#
# @param source_animation: Animation - Source animation to jitter
# @param intensity: int - Jitter intensity (0-255)
# @param frequency: int - Jitter frequency (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return JitterAnimation - A new jitter animation instance
def jitter_position(source_animation, intensity, frequency, strip_length, priority)
return animation.jitter_animation(
source_animation,
intensity,
frequency,
0, # position jitter
50, # position range
30, # color range (unused)
40, # brightness range (unused)
strip_length,
priority,
0, # infinite duration
true, # loop
"jitter_position"
)
end
# Create a color jitter animation
#
# @param source_animation: Animation - Source animation to jitter
# @param intensity: int - Jitter intensity (0-255)
# @param frequency: int - Jitter frequency (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return JitterAnimation - A new jitter animation instance
def jitter_color(source_animation, intensity, frequency, strip_length, priority)
return animation.jitter_animation(
source_animation,
intensity,
frequency,
1, # color jitter
50, # position range (unused)
30, # color range
40, # brightness range (unused)
strip_length,
priority,
0, # infinite duration
true, # loop
"jitter_color"
)
end
# Create a brightness jitter animation
#
# @param source_animation: Animation - Source animation to jitter
# @param intensity: int - Jitter intensity (0-255)
# @param frequency: int - Jitter frequency (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return JitterAnimation - A new jitter animation instance
def jitter_brightness(source_animation, intensity, frequency, strip_length, priority)
return animation.jitter_animation(
source_animation,
intensity,
frequency,
2, # brightness jitter
50, # position range (unused)
30, # color range (unused)
40, # brightness range
strip_length,
priority,
0, # infinite duration
true, # loop
"jitter_brightness"
)
end
# Create a full jitter animation (all types)
#
# @param source_animation: Animation - Source animation to jitter
# @param intensity: int - Jitter intensity (0-255)
# @param frequency: int - Jitter frequency (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return JitterAnimation - A new jitter animation instance
def jitter_all(source_animation, intensity, frequency, strip_length, priority)
return animation.jitter_animation(
source_animation,
intensity,
frequency,
3, # all jitter types
50, # position range
30, # color range
40, # brightness range
strip_length,
priority,
0, # infinite duration
true, # loop
"jitter_all"
)
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,429 @@
# 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
var color # Color for noise colors (32-bit ARGB value or ValueProvider instance)
var scale # Noise scale/frequency (0-255, higher = more detailed)
var speed # Animation speed (0-255)
var octaves # Number of noise octaves for fractal noise (1-4)
var persistence # Persistence for octaves (0-255)
var seed # Random seed for reproducible noise
var strip_length # Length of the LED strip
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
# Initialize a new Noise animation
#
# @param color: int|ValueProvider - Color for noise colors (32-bit ARGB value or ValueProvider instance), defaults to rainbow if nil
# @param scale: int - Noise scale (0-255), defaults to 50 if nil
# @param speed: int - Animation speed (0-255), defaults to 30 if nil
# @param octaves: int - Number of octaves (1-4), defaults to 1 if nil
# @param persistence: int - Octave persistence (0-255), defaults to 128 if nil
# @param seed: int - Random seed, defaults to random if nil
# @param strip_length: int - Length of LED strip, defaults to 30 if nil
# @param priority: int - Rendering priority, defaults to 10 if nil
# @param duration: int - Duration in ms, defaults to 0 (infinite) if nil
# @param loop: bool - Whether to loop, defaults to true if nil
# @param name: string - Animation name, defaults to "noise" if nil
def init(color, scale, speed, octaves, persistence, seed, strip_length, priority, duration, loop, name)
# Call parent constructor
super(self).init(priority, duration, loop != nil ? loop : true, 255, name != nil ? name : "noise")
# Set initial values with defaults
if color == nil
# Default rainbow palette
var rainbow_provider = animation.rich_palette_color_provider(
animation.PALETTE_RAINBOW,
5000, # cycle period
1, # sine transition
255 # full brightness
)
rainbow_provider.set_range(0, 255)
self.color = rainbow_provider
elif type(color) == "int"
# Single color - create a gradient 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((color >> 16) & 0xFF, 1) # R
palette.add((color >> 8) & 0xFF, 1) # G
palette.add(color & 0xFF, 1) # B
var gradient_provider = animation.rich_palette_color_provider(
palette, 5000, 1, 255
)
gradient_provider.set_range(0, 255)
self.color = gradient_provider
else
# Assume it's already a color provider
self.color = color
end
# Set parameters with defaults
self.scale = scale != nil ? scale : 50
self.speed = speed != nil ? speed : 30
self.octaves = octaves != nil ? octaves : 1
self.persistence = persistence != nil ? persistence : 128
self.strip_length = strip_length != nil ? strip_length : 30
# Initialize seed
if seed != nil
self.seed = seed
else
var current_time = tasmota.millis()
self.seed = current_time % 65536
end
# Initialize arrays and state
self.current_colors = []
self.current_colors.resize(self.strip_length)
self.time_offset = 0
# Initialize noise table for performance
self._init_noise_table()
# Initialize colors to black
var i = 0
while i < self.strip_length
self.current_colors[i] = 0xFF000000
i += 1
end
# Register parameters
self.register_param("color", {"default": nil})
self.register_param("scale", {"min": 1, "max": 255, "default": 50})
self.register_param("speed", {"min": 0, "max": 255, "default": 30})
self.register_param("octaves", {"min": 1, "max": 4, "default": 1})
self.register_param("persistence", {"min": 0, "max": 255, "default": 128})
self.register_param("seed", {"min": 0, "max": 65535, "default": 12345})
self.register_param("strip_length", {"min": 1, "max": 1000, "default": 30})
# Set initial parameter values
self.set_param("color", self.color)
self.set_param("scale", self.scale)
self.set_param("speed", self.speed)
self.set_param("octaves", self.octaves)
self.set_param("persistence", self.persistence)
self.set_param("seed", self.seed)
self.set_param("strip_length", self.strip_length)
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 rng_state = self.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
# Handle parameter changes
def on_param_changed(name, value)
if name == "color"
if value == nil
# Reset to default rainbow palette
var rainbow_provider = animation.rich_palette_color_provider(
animation.PALETTE_RAINBOW,
5000,
1,
255
)
rainbow_provider.set_range(0, 255)
self.color = rainbow_provider
else
self.color = value
end
elif name == "scale"
self.scale = value
elif name == "speed"
self.speed = value
elif name == "octaves"
self.octaves = value
elif name == "persistence"
self.persistence = value
elif name == "seed"
self.seed = value
self._init_noise_table()
elif name == "strip_length"
self.current_colors.resize(value)
var i = 0
while i < value
if self.current_colors[i] == nil
self.current_colors[i] = 0xFF000000
end
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 frequency = self.scale
var max_value = 0
var octave = 0
while octave < self.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, self.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
if self.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(self.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 i = 0
while i < self.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(self.color) && self.color.get_color_for_value != nil
color = self.color.get_color_for_value(noise_value, 0)
else
# Use resolve_value with noise influence
color = self.resolve_value(self.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 i = 0
while i < self.strip_length
if i < frame.width
frame.set_pixel_color(i, self.current_colors[i])
end
i += 1
end
return true
end
# Set the color
#
# @param color: int|ValueProvider - 32-bit color value in ARGB format (0xAARRGGBB) or a ValueProvider instance
# @return self for method chaining
def set_color(color)
self.set_param("color", color)
return self
end
# Set the noise scale
#
# @param scale: int - Noise scale/frequency (0-255)
# @return self for method chaining
def set_scale(scale)
self.set_param("scale", scale)
return self
end
# Set the animation speed
#
# @param speed: int - Animation speed (0-255)
# @return self for method chaining
def set_speed(speed)
self.set_param("speed", speed)
return self
end
# Set the number of octaves
#
# @param octaves: int - Number of noise octaves (1-4)
# @return self for method chaining
def set_octaves(octaves)
self.set_param("octaves", octaves)
return self
end
# Set the persistence
#
# @param persistence: int - Octave persistence (0-255)
# @return self for method chaining
def set_persistence(persistence)
self.set_param("persistence", persistence)
return self
end
# Set the random seed
#
# @param seed: int - Random seed for reproducible noise
# @return self for method chaining
def set_seed(seed)
self.set_param("seed", seed)
return self
end
# Set the strip length
#
# @param length: int - Length of the LED strip
# @return self for method chaining
def set_strip_length(length)
self.set_param("strip_length", length)
return self
end
# String representation
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"NoiseAnimation(color={color_str}, scale={self.scale}, speed={self.speed}, octaves={self.octaves}, priority={self.priority}, running={self.is_running})"
end
end
# Global constructor functions
# Create a rainbow noise animation
#
# @param scale: int - Noise scale (0-255)
# @param speed: int - Animation speed (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return NoiseAnimation - A new noise animation instance
def noise_rainbow(scale, speed, strip_length, priority)
return animation.noise_animation(
nil, # default rainbow
scale,
speed,
1, # single octave
128, # default persistence
nil, # random seed
strip_length,
priority,
0, # infinite duration
true, # loop
"noise_rainbow"
)
end
# Create a single color noise animation
#
# @param color: int - Base color (32-bit ARGB)
# @param scale: int - Noise scale (0-255)
# @param speed: int - Animation speed (0-255)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return NoiseAnimation - A new noise animation instance
def noise_single_color(color, scale, speed, strip_length, priority)
return animation.noise_animation(
color,
scale,
speed,
1, # single octave
128, # default persistence
nil, # random seed
strip_length,
priority,
0, # infinite duration
true, # loop
"noise_single"
)
end
# Create a fractal noise animation with multiple octaves
#
# @param color_source: int or ColorProvider - Color source
# @param scale: int - Base noise scale (0-255)
# @param speed: int - Animation speed (0-255)
# @param octaves: int - Number of octaves (1-4)
# @param strip_length: int - Length of LED strip
# @param priority: int - Rendering priority
# @return NoiseAnimation - A new noise animation instance
def noise_fractal(color_source, scale, speed, octaves, strip_length, priority)
return animation.noise_animation(
color_source,
scale,
speed,
octaves,
128, # default persistence
nil, # random seed
strip_length,
priority,
0, # infinite duration
true, # loop
"noise_fractal"
)
end
return {'noise_animation': NoiseAnimation, 'noise_rainbow': noise_rainbow, 'noise_single_color': noise_single_color, 'noise_fractal': noise_fractal}

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