Preview of Berry animation framework (#23740)
* Preview of Berry animation framework * fix comet and compilation for safeboot
This commit is contained in:
parent
4d2162507a
commit
ca934bae33
@ -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
|
||||
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
215
lib/libesp32/berry_animation/README.md
Normal file
215
lib/libesp32/berry_animation/README.md
Normal 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)!
|
||||
@ -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
|
||||
@ -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
|
||||
46
lib/libesp32/berry_animation/anim_examples/candy_cane.anim
Normal file
46
lib/libesp32/berry_animation/anim_examples/candy_cane.anim
Normal 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
|
||||
@ -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
|
||||
39
lib/libesp32/berry_animation/anim_examples/comet_chase.anim
Normal file
39
lib/libesp32/berry_animation/anim_examples/comet_chase.anim
Normal 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
|
||||
@ -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.
|
||||
|
||||
126
lib/libesp32/berry_animation/anim_examples/compiled/README.md
Normal file
126
lib/libesp32/berry_animation/anim_examples/compiled/README.md
Normal 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
|
||||
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
132
lib/libesp32/berry_animation/anim_examples/compiled/lava_lamp.be
Normal file
132
lib/libesp32/berry_animation/anim_examples/compiled/lava_lamp.be
Normal 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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
140
lib/libesp32/berry_animation/anim_examples/compiled/neon_glow.be
Normal file
140
lib/libesp32/berry_animation/anim_examples/compiled/neon_glow.be
Normal 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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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"
|
||||
@ -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()
|
||||
@ -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"
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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")
|
||||
@ -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()
|
||||
52
lib/libesp32/berry_animation/anim_examples/disco_strobe.anim
Normal file
52
lib/libesp32/berry_animation/anim_examples/disco_strobe.anim
Normal 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
|
||||
32
lib/libesp32/berry_animation/anim_examples/fire_flicker.anim
Normal file
32
lib/libesp32/berry_animation/anim_examples/fire_flicker.anim
Normal 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
|
||||
@ -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
|
||||
63
lib/libesp32/berry_animation/anim_examples/lava_lamp.anim
Normal file
63
lib/libesp32/berry_animation/anim_examples/lava_lamp.anim
Normal 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
|
||||
@ -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
|
||||
57
lib/libesp32/berry_animation/anim_examples/matrix_rain.anim
Normal file
57
lib/libesp32/berry_animation/anim_examples/matrix_rain.anim
Normal 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
|
||||
@ -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
|
||||
65
lib/libesp32/berry_animation/anim_examples/neon_glow.anim
Normal file
65
lib/libesp32/berry_animation/anim_examples/neon_glow.anim
Normal 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
|
||||
51
lib/libesp32/berry_animation/anim_examples/ocean_waves.anim
Normal file
51
lib/libesp32/berry_animation/anim_examples/ocean_waves.anim
Normal 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
|
||||
40
lib/libesp32/berry_animation/anim_examples/palette_demo.anim
Normal file
40
lib/libesp32/berry_animation/anim_examples/palette_demo.anim
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
57
lib/libesp32/berry_animation/anim_examples/plasma_wave.anim
Normal file
57
lib/libesp32/berry_animation/anim_examples/plasma_wave.anim
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
733
lib/libesp32/berry_animation/docs/API_REFERENCE.md
Normal file
733
lib/libesp32/berry_animation/docs/API_REFERENCE.md
Normal 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.
|
||||
568
lib/libesp32/berry_animation/docs/EXAMPLES.md
Normal file
568
lib/libesp32/berry_animation/docs/EXAMPLES.md
Normal 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! 🎨✨
|
||||
231
lib/libesp32/berry_animation/docs/PROJECT_STRUCTURE.md
Normal file
231
lib/libesp32/berry_animation/docs/PROJECT_STRUCTURE.md
Normal 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.
|
||||
253
lib/libesp32/berry_animation/docs/QUICK_START.md
Normal file
253
lib/libesp32/berry_animation/docs/QUICK_START.md
Normal 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! 🎨✨
|
||||
599
lib/libesp32/berry_animation/docs/TROUBLESHOOTING.md
Normal file
599
lib/libesp32/berry_animation/docs/TROUBLESHOOTING.md
Normal 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.
|
||||
16
lib/libesp32/berry_animation/library.json
Normal file
16
lib/libesp32/berry_animation/library.json
Normal 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" ]
|
||||
}
|
||||
}
|
||||
109
lib/libesp32/berry_animation/solidify_all.be
Normal file
109
lib/libesp32/berry_animation/solidify_all.be
Normal 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
|
||||
58
lib/libesp32/berry_animation/src/CHANGELOG.md
Normal file
58
lib/libesp32/berry_animation/src/CHANGELOG.md
Normal 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
|
||||
21
lib/libesp32/berry_animation/src/LICENSE
Normal file
21
lib/libesp32/berry_animation/src/LICENSE
Normal 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.
|
||||
215
lib/libesp32/berry_animation/src/README.md
Normal file
215
lib/libesp32/berry_animation/src/README.md
Normal 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)!
|
||||
169
lib/libesp32/berry_animation/src/animation.be
Normal file
169
lib/libesp32/berry_animation/src/animation.be
Normal 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
|
||||
208
lib/libesp32/berry_animation/src/animations/pulse_animation.be
Normal file
208
lib/libesp32/berry_animation/src/animations/pulse_animation.be
Normal 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}
|
||||
30
lib/libesp32/berry_animation/src/be_animation.c
Normal file
30
lib/libesp32/berry_animation/src/be_animation.c
Normal 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
|
||||
8
lib/libesp32/berry_animation/src/berry_animation.h
Normal file
8
lib/libesp32/berry_animation/src/berry_animation.h
Normal file
@ -0,0 +1,8 @@
|
||||
// force include of module by including this file
|
||||
|
||||
#ifndef __BERRY_ANIMATION__
|
||||
#define __BERRY_ANIMATION__
|
||||
|
||||
|
||||
|
||||
#endif // __BERRY_ANIMATION__
|
||||
166
lib/libesp32/berry_animation/src/core/animation_base.be
Normal file
166
lib/libesp32/berry_animation/src/core/animation_base.be
Normal 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}
|
||||
392
lib/libesp32/berry_animation/src/core/animation_engine.be
Normal file
392
lib/libesp32/berry_animation/src/core/animation_engine.be
Normal 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}
|
||||
265
lib/libesp32/berry_animation/src/core/event_handler.be
Normal file
265
lib/libesp32/berry_animation/src/core/event_handler.be
Normal 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,
|
||||
}
|
||||
584
lib/libesp32/berry_animation/src/core/frame_buffer.be
Normal file
584
lib/libesp32/berry_animation/src/core/frame_buffer.be
Normal 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}
|
||||
381
lib/libesp32/berry_animation/src/core/pattern_base.be
Normal file
381
lib/libesp32/berry_animation/src/core/pattern_base.be
Normal 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}
|
||||
165
lib/libesp32/berry_animation/src/core/sequence_manager.be
Normal file
165
lib/libesp32/berry_animation/src/core/sequence_manager.be
Normal 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}
|
||||
44
lib/libesp32/berry_animation/src/core/user_functions.be
Normal file
44
lib/libesp32/berry_animation/src/core/user_functions.be
Normal 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
|
||||
}
|
||||
750
lib/libesp32/berry_animation/src/docs/API_REFERENCE.md
Normal file
750
lib/libesp32/berry_animation/src/docs/API_REFERENCE.md
Normal 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.
|
||||
568
lib/libesp32/berry_animation/src/docs/EXAMPLES.md
Normal file
568
lib/libesp32/berry_animation/src/docs/EXAMPLES.md
Normal 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! 🎨✨
|
||||
231
lib/libesp32/berry_animation/src/docs/PROJECT_STRUCTURE.md
Normal file
231
lib/libesp32/berry_animation/src/docs/PROJECT_STRUCTURE.md
Normal 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.
|
||||
253
lib/libesp32/berry_animation/src/docs/QUICK_START.md
Normal file
253
lib/libesp32/berry_animation/src/docs/QUICK_START.md
Normal 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! 🎨✨
|
||||
599
lib/libesp32/berry_animation/src/docs/TROUBLESHOOTING.md
Normal file
599
lib/libesp32/berry_animation/src/docs/TROUBLESHOOTING.md
Normal 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.
|
||||
542
lib/libesp32/berry_animation/src/dsl/lexer.be
Normal file
542
lib/libesp32/berry_animation/src/dsl/lexer.be
Normal 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
|
||||
}
|
||||
176
lib/libesp32/berry_animation/src/dsl/runtime.be
Normal file
176
lib/libesp32/berry_animation/src/dsl/runtime.be
Normal 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
|
||||
}
|
||||
508
lib/libesp32/berry_animation/src/dsl/token.be
Normal file
508
lib/libesp32/berry_animation/src/dsl/token.be
Normal 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
|
||||
}
|
||||
1145
lib/libesp32/berry_animation/src/dsl/transpiler.be
Normal file
1145
lib/libesp32/berry_animation/src/dsl/transpiler.be
Normal file
File diff suppressed because it is too large
Load Diff
319
lib/libesp32/berry_animation/src/effects/bounce.be
Normal file
319
lib/libesp32/berry_animation/src/effects/bounce.be
Normal 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}
|
||||
203
lib/libesp32/berry_animation/src/effects/breathe.be
Normal file
203
lib/libesp32/berry_animation/src/effects/breathe.be
Normal 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}
|
||||
337
lib/libesp32/berry_animation/src/effects/comet.be
Normal file
337
lib/libesp32/berry_animation/src/effects/comet.be
Normal 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}
|
||||
258
lib/libesp32/berry_animation/src/effects/crenel_position.be
Normal file
258
lib/libesp32/berry_animation/src/effects/crenel_position.be
Normal 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}
|
||||
166
lib/libesp32/berry_animation/src/effects/filled.be
Normal file
166
lib/libesp32/berry_animation/src/effects/filled.be
Normal 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}
|
||||
409
lib/libesp32/berry_animation/src/effects/fire.be
Normal file
409
lib/libesp32/berry_animation/src/effects/fire.be
Normal 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}
|
||||
424
lib/libesp32/berry_animation/src/effects/gradient.be
Normal file
424
lib/libesp32/berry_animation/src/effects/gradient.be
Normal 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}
|
||||
398
lib/libesp32/berry_animation/src/effects/jitter.be
Normal file
398
lib/libesp32/berry_animation/src/effects/jitter.be
Normal 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}
|
||||
429
lib/libesp32/berry_animation/src/effects/noise.be
Normal file
429
lib/libesp32/berry_animation/src/effects/noise.be
Normal 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
Loading…
Reference in New Issue
Block a user