Berry update of preview of animation framework (#23816)
This commit is contained in:
parent
b5775895d2
commit
772ba227e2
@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Epdiy library from v1.0.0 to v2.0.0
|
||||
- ESP8266 platform update from 2025.07.00 to 2025.08.00 (#23801)
|
||||
- Support for ESP32-C5 (#23804)
|
||||
- Berry update of preview of animation framework
|
||||
|
||||
### Fixed
|
||||
- Syslog RFC5424 compliance (#23509)
|
||||
|
||||
@ -86,6 +86,9 @@ be_extern_native_module(haspmota);
|
||||
#ifdef USE_WS2812
|
||||
#ifdef USE_BERRY_ANIMATION
|
||||
be_extern_native_module(animation);
|
||||
#ifdef USE_BERRY_ANIMATION_DSL
|
||||
be_extern_native_module(animation_dsl);
|
||||
#endif // USE_BERRY_ANIMATION_DSL
|
||||
#endif // USE_BERRY_ANIMATION
|
||||
#endif // USE_WS2812
|
||||
be_extern_native_module(matter);
|
||||
@ -225,6 +228,9 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = {
|
||||
#ifdef USE_WS2812
|
||||
#ifdef USE_BERRY_ANIMATION
|
||||
&be_native_module(animation),
|
||||
#ifdef USE_BERRY_ANIMATION_DSL
|
||||
&be_native_module(animation_dsl),
|
||||
#endif // USE_BERRY_ANIMATION_DSL
|
||||
#endif // USE_BERRY_ANIMATION
|
||||
#endif // USE_WS2812
|
||||
#endif // TASMOTA
|
||||
|
||||
@ -178,6 +178,9 @@ static bmapnode* insert(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
|
||||
|
||||
static bmapnode* find(bvm *vm, bmap *map, bvalue *key, uint32_t hash)
|
||||
{
|
||||
if (map->size == 0) { /* this situation happens only for solidified empty maps that are compacted */
|
||||
return NULL;
|
||||
}
|
||||
bmapnode *slot = hash2slot(map, hash);
|
||||
if (isnil(slot)) {
|
||||
return NULL;
|
||||
|
||||
@ -1,215 +1,221 @@
|
||||
# Tasmota Berry Animation Framework
|
||||
# Berry Animation Framework
|
||||
|
||||
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using Berry scripting language.
|
||||
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using a simple Domain-Specific Language (DSL).
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, twinkle, and more
|
||||
- **🌈 Advanced Color System** - Palettes, gradients, color cycling with smooth transitions
|
||||
- **📝 Domain-Specific Language (DSL)** - Write animations in intuitive, declarative syntax
|
||||
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, sparkle, wave, and more
|
||||
- **🌈 Advanced Color System** - Predefined palettes, custom gradients, smooth color cycling
|
||||
- **📝 Simple DSL Syntax** - Write animations in intuitive, declarative language
|
||||
- **⚡ High Performance** - Optimized for embedded systems with minimal memory usage
|
||||
- **🔧 Extensible** - Create custom animations and user-defined functions
|
||||
- **🔧 Extensible** - Create custom animations and effects
|
||||
- **🎯 Position-Based Effects** - Precise control over individual LED positions
|
||||
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with value providers
|
||||
- **🎭 Event System** - Responsive animations that react to button presses, timers, and sensors
|
||||
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with oscillating values
|
||||
- **🎭 Sequences** - Create complex shows with timing and loops
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Basic Berry Animation
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
# Create LED strip (60 LEDs)
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create a pulsing red animation
|
||||
var pulse_red = animation.pulse(
|
||||
animation.solid(0xFFFF0000), # Red color
|
||||
2000, # 2 second period
|
||||
50, # Min brightness (0-255)
|
||||
255 # Max brightness (0-255)
|
||||
)
|
||||
|
||||
# Start the animation
|
||||
engine.add_animation(pulse_red)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### 2. Using the Animation DSL
|
||||
|
||||
Create a file `my_animation.anim`:
|
||||
### Simple Pulsing Animation
|
||||
|
||||
```dsl
|
||||
# Define colors
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Create animations
|
||||
animation pulse_red = pulse(solid(red), 2s, 20%, 100%)
|
||||
animation pulse_blue = pulse(solid(blue), 3s, 30%, 100%)
|
||||
# Create pulsing animation
|
||||
animation pulse_red = pulsating_animation(color=red, period=3s)
|
||||
|
||||
# Create a sequence
|
||||
sequence demo {
|
||||
play pulse_red for 5s
|
||||
wait 1s
|
||||
play pulse_blue for 5s
|
||||
repeat 3 times:
|
||||
play pulse_red for 2s
|
||||
play pulse_blue for 2s
|
||||
}
|
||||
|
||||
run demo
|
||||
# Run it
|
||||
run pulse_red
|
||||
```
|
||||
|
||||
Load and run the DSL:
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var runtime = animation.DSLRuntime(animation.create_engine(strip))
|
||||
runtime.load_dsl_file("my_animation.anim")
|
||||
```
|
||||
|
||||
### 3. Palette-Based Animations
|
||||
### Rainbow Color Cycling
|
||||
|
||||
```dsl
|
||||
# Define a fire palette
|
||||
palette fire_colors = [
|
||||
(0, #000000), # Black
|
||||
(64, #800000), # Dark red
|
||||
(128, #FF0000), # Red
|
||||
(192, #FF8000), # Orange
|
||||
(255, #FFFF00) # Yellow
|
||||
# Use predefined rainbow palette
|
||||
animation rainbow_cycle = rich_palette(
|
||||
palette=PALETTE_RAINBOW,
|
||||
cycle_period=5s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run rainbow_cycle
|
||||
```
|
||||
|
||||
### Custom Color Palette
|
||||
|
||||
```dsl
|
||||
# Define a sunset palette
|
||||
palette sunset = [
|
||||
(0, #191970), # Midnight blue
|
||||
(64, purple), # Purple
|
||||
(128, #FF69B4), # Hot pink
|
||||
(192, orange), # Orange
|
||||
(255, yellow) # Yellow
|
||||
]
|
||||
|
||||
# Create fire animation
|
||||
animation fire_effect = rich_palette_animation(fire_colors, 3s, smooth, 255)
|
||||
# Create palette animation
|
||||
animation sunset_glow = rich_palette(
|
||||
palette=sunset,
|
||||
cycle_period=8s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run fire_effect
|
||||
run sunset_glow
|
||||
```
|
||||
|
||||
### Animation Sequences
|
||||
|
||||
```dsl
|
||||
animation red_pulse = pulsating_animation(color=red, period=2s)
|
||||
animation green_pulse = pulsating_animation(color=green, period=2s)
|
||||
animation blue_pulse = pulsating_animation(color=blue, period=2s)
|
||||
|
||||
sequence rgb_show {
|
||||
play red_pulse for 3s
|
||||
wait 500ms
|
||||
play green_pulse for 3s
|
||||
wait 500ms
|
||||
play blue_pulse for 3s
|
||||
|
||||
repeat 2 times:
|
||||
play red_pulse for 1s
|
||||
play green_pulse for 1s
|
||||
play blue_pulse for 1s
|
||||
}
|
||||
|
||||
run rgb_show
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### User Guides
|
||||
### Getting Started
|
||||
- **[Quick Start Guide](docs/QUICK_START.md)** - Get up and running in 5 minutes
|
||||
- **[API Reference](docs/API_REFERENCE.md)** - Complete Berry API documentation
|
||||
- **[Examples](docs/EXAMPLES.md)** - Curated examples with explanations
|
||||
- **[DSL Reference](docs/DSL_REFERENCE.md)** - Complete DSL syntax and features
|
||||
- **[Examples](docs/EXAMPLES.md)** - Comprehensive examples and tutorials
|
||||
|
||||
### Reference
|
||||
- **[Animation Class Hierarchy](docs/ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
|
||||
- **[Oscillation Patterns](docs/OSCILLATION_PATTERNS.md)** - Dynamic value patterns and waveforms
|
||||
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
|
||||
### DSL (Domain-Specific Language)
|
||||
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax guide
|
||||
- **[DSL Grammar](.kiro/specs/berry-animation-framework/dsl-grammar.md)** - Formal grammar specification
|
||||
- **[Palette Guide](.kiro/specs/berry-animation-framework/palette-quick-reference.md)** - Working with color palettes
|
||||
|
||||
### Advanced Topics
|
||||
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom animation functions
|
||||
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Responsive, interactive animations
|
||||
- **[Project Structure](docs/PROJECT_STRUCTURE.md)** - Navigate the codebase
|
||||
|
||||
### Framework Design
|
||||
- **[Requirements](.kiro/specs/berry-animation-framework/requirements.md)** - Project goals and requirements (✅ Complete)
|
||||
- **[Architecture](.kiro/specs/berry-animation-framework/design.md)** - Framework design and architecture
|
||||
- **[Future Features](.kiro/specs/berry-animation-framework/future_features.md)** - Planned enhancements
|
||||
### Advanced
|
||||
- **[User Functions](docs/USER_FUNCTIONS.md)** - Create custom animation functions
|
||||
- **[Animation Development](docs/ANIMATION_DEVELOPMENT.md)** - Create custom animations
|
||||
|
||||
## 🎯 Core Concepts
|
||||
|
||||
### Unified Architecture
|
||||
The framework uses a **unified pattern-animation architecture** where `Animation` extends `Pattern`. This means:
|
||||
- Animations ARE patterns with temporal behavior
|
||||
- Infinite composition: animations can use other animations as base patterns
|
||||
- Consistent API across all visual elements
|
||||
### DSL-First Design
|
||||
Write animations using simple, declarative syntax:
|
||||
```dsl
|
||||
animation fire_effect = fire_animation(intensity=200, cooling=55, sparking=120)
|
||||
run fire_effect
|
||||
```
|
||||
|
||||
### Animation Engine
|
||||
The `AnimationEngine` is the heart of the framework:
|
||||
- Manages multiple animations with priority-based layering
|
||||
- Handles timing, blending, and LED output
|
||||
- Integrates with Tasmota's `fast_loop` for smooth performance
|
||||
### Dynamic Parameters
|
||||
Use oscillating values to create complex effects:
|
||||
```dsl
|
||||
animation pulsing_comet = comet_animation(
|
||||
color=red,
|
||||
tail_length=smooth_oscillator(5, 15, 3s),
|
||||
speed=2
|
||||
)
|
||||
```
|
||||
|
||||
### Value Providers
|
||||
Dynamic parameters that change over time:
|
||||
- **Static values**: `solid(red)`
|
||||
- **Oscillators**: `pulse(solid(red), smooth(50, 255, 2s))`
|
||||
- **Color providers**: `rich_palette_animation(fire_palette, 3s)`
|
||||
### Color Palettes
|
||||
Rich color transitions with predefined or custom palettes:
|
||||
```dsl
|
||||
palette custom = [(0, blue), (128, purple), (255, pink)]
|
||||
animation palette_cycle = rich_palette(palette=custom, cycle_period=4s)
|
||||
```
|
||||
|
||||
## 🎨 Animation Types
|
||||
|
||||
### Basic Animations
|
||||
- **`solid(color)`** - Static color fill
|
||||
- **`pulse(pattern, period, min, max)`** - Pulsing brightness
|
||||
- **`breathe(color, period)`** - Smooth breathing effect
|
||||
### Basic Effects
|
||||
- **Pulse** - Breathing/pulsing effects with smooth transitions
|
||||
- **Sparkle** - Random twinkling and starfield effects
|
||||
- **Fire** - Realistic fire simulation with warm colors
|
||||
- **Comet** - Moving comet with customizable tail
|
||||
|
||||
### Pattern-Based Animations
|
||||
- **`rich_palette_animation(palette, period, easing, brightness)`** - Palette color cycling
|
||||
- **`gradient(color1, color2, ...)`** - Color gradients
|
||||
- **`fire_animation(intensity, speed)`** - Realistic fire simulation
|
||||
### Color Animations
|
||||
- **Rich Palette** - Smooth color transitions using predefined palettes
|
||||
- **Color Cycling** - Custom color sequences with smooth blending
|
||||
- **Gradient** - Linear and radial color gradients
|
||||
- **Plasma** - Classic plasma effects with sine wave interference
|
||||
|
||||
### Position-Based Animations
|
||||
- **`pulse_position_animation(color, pos, size, fade)`** - Localized pulse
|
||||
- **`comet_animation(color, tail_length, speed)`** - Moving comet effect
|
||||
- **`twinkle_animation(color, density, speed)`** - Twinkling stars
|
||||
### Pattern Effects
|
||||
- **Wave** - Mathematical waveforms (sine, triangle, square, sawtooth)
|
||||
- **Noise** - Organic patterns using Perlin noise
|
||||
- **Position-Based** - Precise control over individual LED positions
|
||||
|
||||
### Motion Effects
|
||||
- **`shift_left(pattern, speed)`** - Move pattern left
|
||||
- **`shift_right(pattern, speed)`** - Move pattern right
|
||||
- **`bounce(pattern, period)`** - Bouncing motion
|
||||
- **Bounce** - Physics-based bouncing with gravity and damping
|
||||
- **Shift** - Scrolling and translation effects
|
||||
- **Scale** - Size transformation and breathing effects
|
||||
- **Jitter** - Add random variations to any animation
|
||||
|
||||
## 🔧 Installation
|
||||
|
||||
### For Tasmota Development
|
||||
1. Copy the `lib/libesp32/berry_animation/` directory to your Tasmota build
|
||||
2. The framework will be available as the `animation` module
|
||||
3. Use `import animation` in your Berry scripts
|
||||
### Prerequisites
|
||||
- Tasmota firmware with Berry support
|
||||
- Addressable LED strip (WS2812, SK6812, etc.)
|
||||
|
||||
### For Testing/Development
|
||||
1. Install Berry interpreter with Tasmota extensions
|
||||
2. Set module path: `berry -m lib/libesp32/berry_animation`
|
||||
3. Run examples: `berry examples/simple_engine_test.be`
|
||||
### Setup
|
||||
1. **Enable Berry** in Tasmota configuration
|
||||
2. **Configure LED strip** using Tasmota's LED configuration
|
||||
3. **Import the framework**:
|
||||
```berry
|
||||
import animation
|
||||
```
|
||||
4. **Create your first animation** using the DSL
|
||||
|
||||
## 🧪 Examples
|
||||
## 🌈 Predefined Palettes
|
||||
|
||||
The framework includes comprehensive examples:
|
||||
The framework includes several built-in color palettes:
|
||||
|
||||
### Berry Examples
|
||||
- **[Basic Engine](lib/libesp32/berry_animation/examples/simple_engine_test.be)** - Simple animation setup
|
||||
- **[Color Providers](lib/libesp32/berry_animation/examples/color_provider_demo.be)** - Dynamic color effects
|
||||
- **[Position Effects](lib/libesp32/berry_animation/examples/pulse_position_animation_demo.be)** - Localized animations
|
||||
- **[Event System](lib/libesp32/berry_animation/examples/event_system_demo.be)** - Interactive animations
|
||||
- **PALETTE_RAINBOW** - Standard 7-color rainbow (Red → Orange → Yellow → Green → Blue → Indigo → Violet)
|
||||
- **PALETTE_RGB** - Simple RGB cycle (Red → Green → Blue)
|
||||
- **PALETTE_FIRE** - Warm fire colors (Black → Dark Red → Red → Orange → Yellow)
|
||||
- **PALETTE_SUNSET_TICKS** - Sunset colors (Orange Red → Dark Orange → Gold → Hot Pink → Purple → Midnight Blue)
|
||||
- **PALETTE_OCEAN** - Blue and green ocean tones (Navy → Blue → Cyan → Spring Green → Green)
|
||||
- **PALETTE_FOREST** - Various green forest tones (Dark Green → Forest Green → Lime Green → Mint Green → Light Green)
|
||||
|
||||
### DSL Examples
|
||||
- **[Aurora Borealis](anim_examples/aurora_borealis.anim)** - Northern lights effect
|
||||
- **[Breathing Colors](anim_examples/breathing_colors.anim)** - Smooth color breathing
|
||||
- **[Fire Effect](anim_examples/fire_demo.anim)** - Realistic fire simulation
|
||||
```dsl
|
||||
# Use any predefined palette
|
||||
animation ocean_waves = rich_palette(
|
||||
palette=PALETTE_OCEAN,
|
||||
cycle_period=8s,
|
||||
transition_type=1
|
||||
)
|
||||
run ocean_waves
|
||||
```
|
||||
|
||||
## ⚡ Performance
|
||||
|
||||
Optimized for embedded systems:
|
||||
|
||||
- **Memory Efficient** - Minimal RAM usage
|
||||
- **CPU Optimized** - Efficient rendering algorithms
|
||||
- **Scalable** - Handles strips from 10 to 1000+ LEDs
|
||||
- **Real-time** - Smooth 30+ FPS animation
|
||||
|
||||
### Performance Tips
|
||||
- Use longer animation periods (2-5 seconds) for smoother performance
|
||||
- Limit simultaneous animations (3-5 max recommended)
|
||||
- Consider strip length when designing complex effects
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Run all tests
|
||||
berry lib/libesp32/berry_animation/tests/test_all.be
|
||||
|
||||
# Run specific test
|
||||
berry lib/libesp32/berry_animation/tests/animation_engine_test.be
|
||||
```
|
||||
|
||||
### Code Style
|
||||
- Follow Berry language conventions
|
||||
- Use descriptive variable names
|
||||
- Include comprehensive comments
|
||||
- Add test coverage for new features
|
||||
Contributions are welcome! Areas for contribution:
|
||||
- New animation effects
|
||||
- Performance optimizations
|
||||
- Documentation improvements
|
||||
- Example animations
|
||||
- Bug fixes and testing
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is part of the Tasmota ecosystem and follows the same licensing terms.
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **Tasmota Team** - For the excellent IoT platform
|
||||
- **Berry Language** - For the lightweight scripting language
|
||||
- **Community Contributors** - For testing, feedback, and improvements
|
||||
This project is licensed under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
**Ready to create amazing LED animations?** Start with the [Quick Start Guide](docs/QUICK_START.md)!
|
||||
**Happy Animating!** 🎨✨
|
||||
@ -23,10 +23,10 @@ palette aurora_purple = [
|
||||
|
||||
# Base aurora animation with slow flowing colors
|
||||
animation aurora_base = rich_palette_animation(
|
||||
aurora_colors, # palette
|
||||
10s, # cycle period
|
||||
smooth, # transition type (explicit for clarity)
|
||||
180 # brightness (dimmed for aurora effect)
|
||||
palette=aurora_colors, # palette
|
||||
cycle_period=10s, # cycle period
|
||||
transition_type=SINE, # transition type (explicit for clarity)
|
||||
brightness=180 # brightness (dimmed for aurora effect)
|
||||
)
|
||||
|
||||
sequence demo {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Breathing Colors - Slow color breathing effect
|
||||
# Gentle pulsing through different colors
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define breathing colors
|
||||
color breathe_red = 0xFF0000
|
||||
@ -21,21 +21,21 @@ palette breathe_palette = [
|
||||
]
|
||||
|
||||
# Create a rich palette color provider
|
||||
pattern palette_pattern = rich_palette_animation(
|
||||
breathe_palette, # palette
|
||||
15s # cycle period (defaults: smooth transition, 255 brightness)
|
||||
color palette_pattern = rich_palette(
|
||||
palette=breathe_palette, # palette
|
||||
cycle_period=15s # cycle period (defaults: smooth transition, 255 brightness)
|
||||
)
|
||||
|
||||
# Create breathing animation using the palette
|
||||
animation breathing = breathe(
|
||||
palette_pattern, # base pattern
|
||||
100, # min brightness
|
||||
255, # max brightness
|
||||
4s # breathing period (4 seconds)
|
||||
animation breathing = breathe_animation(
|
||||
color=palette_pattern, # base animation
|
||||
min_brightness=100, # min brightness
|
||||
max_brightness=255, # max brightness
|
||||
period=4s # breathing period (4 seconds)
|
||||
)
|
||||
|
||||
# Add gentle opacity breathing
|
||||
breathing.opacity = smooth(100, 255, 4s)
|
||||
breathing.opacity = smooth(min_value=100, max_value=255, duration=4s)
|
||||
|
||||
# Start the animation
|
||||
run breathing
|
||||
@ -1,37 +1,37 @@
|
||||
# Candy Cane - Red and white stripes
|
||||
# Classic Christmas candy cane pattern
|
||||
# Classic Christmas candy cane animation
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define stripe colors
|
||||
color candy_red = 0xFF0000
|
||||
color candy_white = 0xFFFFFF
|
||||
|
||||
# Create alternating red and white pattern
|
||||
# Create alternating red and white animation
|
||||
# Using multiple pulse positions to create stripes
|
||||
animation stripe1 = pulse_position_animation(candy_red, 3, 4, 1)
|
||||
animation stripe2 = pulse_position_animation(candy_white, 9, 4, 1)
|
||||
animation stripe3 = pulse_position_animation(candy_red, 15, 4, 1)
|
||||
animation stripe4 = pulse_position_animation(candy_white, 21, 4, 1)
|
||||
animation stripe5 = pulse_position_animation(candy_red, 27, 4, 1)
|
||||
animation stripe6 = pulse_position_animation(candy_white, 33, 4, 1)
|
||||
animation stripe7 = pulse_position_animation(candy_red, 39, 4, 1)
|
||||
animation stripe8 = pulse_position_animation(candy_white, 45, 4, 1)
|
||||
animation stripe9 = pulse_position_animation(candy_red, 51, 4, 1)
|
||||
animation stripe10 = pulse_position_animation(candy_white, 57, 4, 1)
|
||||
animation stripe1 = beacon_animation(color=candy_red, pos=3, beacon_size=4, slew_size=1)
|
||||
animation stripe2 = beacon_animation(color=candy_white, pos=9, beacon_size=4, slew_size=1)
|
||||
animation stripe3 = beacon_animation(color=candy_red, pos=15, beacon_size=4, slew_size=1)
|
||||
animation stripe4 = beacon_animation(color=candy_white, pos=21, beacon_size=4, slew_size=1)
|
||||
animation stripe5 = beacon_animation(color=candy_red, pos=27, beacon_size=4, slew_size=1)
|
||||
animation stripe6 = beacon_animation(color=candy_white, pos=33, beacon_size=4, slew_size=1)
|
||||
animation stripe7 = beacon_animation(color=candy_red, pos=39, beacon_size=4, slew_size=1)
|
||||
animation stripe8 = beacon_animation(color=candy_white, pos=45, beacon_size=4, slew_size=1)
|
||||
animation stripe9 = beacon_animation(color=candy_red, pos=51, beacon_size=4, slew_size=1)
|
||||
animation stripe10 = beacon_animation(color=candy_white, pos=57, beacon_size=4, slew_size=1)
|
||||
|
||||
# Add gentle movement to make it more dynamic
|
||||
set move_speed = 8s
|
||||
stripe1.pos = sawtooth(3, 63, move_speed)
|
||||
stripe2.pos = sawtooth(9, 69, move_speed)
|
||||
stripe3.pos = sawtooth(15, 75, move_speed)
|
||||
stripe4.pos = sawtooth(21, 81, move_speed)
|
||||
stripe5.pos = sawtooth(27, 87, move_speed)
|
||||
stripe6.pos = sawtooth(33, 93, move_speed)
|
||||
stripe7.pos = sawtooth(39, 99, move_speed)
|
||||
stripe8.pos = sawtooth(45, 105, move_speed)
|
||||
stripe9.pos = sawtooth(51, 111, move_speed)
|
||||
stripe10.pos = sawtooth(57, 117, move_speed)
|
||||
stripe1.pos = sawtooth(min_value=3, max_value=63, duration=move_speed)
|
||||
stripe2.pos = sawtooth(min_value=9, max_value=69, duration=move_speed)
|
||||
stripe3.pos = sawtooth(min_value=15, max_value=75, duration=move_speed)
|
||||
stripe4.pos = sawtooth(min_value=21, max_value=81, duration=move_speed)
|
||||
stripe5.pos = sawtooth(min_value=27, max_value=87, duration=move_speed)
|
||||
stripe6.pos = sawtooth(min_value=33, max_value=93, duration=move_speed)
|
||||
stripe7.pos = sawtooth(min_value=39, max_value=99, duration=move_speed)
|
||||
stripe8.pos = sawtooth(min_value=45, max_value=105, duration=move_speed)
|
||||
stripe9.pos = sawtooth(min_value=51, max_value=111, duration=move_speed)
|
||||
stripe10.pos = sawtooth(min_value=57, max_value=117, duration=move_speed)
|
||||
|
||||
# Start all stripes
|
||||
run stripe1
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# Christmas Tree - Festive holiday colors
|
||||
# Green base with colorful ornaments and twinkling
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Green tree base
|
||||
color tree_green = 0x006600
|
||||
animation tree_base = solid(tree_green)
|
||||
animation tree_base = solid(color=tree_green)
|
||||
|
||||
# Define ornament colors
|
||||
palette ornament_colors = [
|
||||
@ -17,38 +17,38 @@ palette ornament_colors = [
|
||||
]
|
||||
|
||||
# Colorful ornaments as twinkling lights
|
||||
pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
|
||||
color ornament_pattern = rich_palette(palette=ornament_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
animation ornaments = twinkle_animation(
|
||||
ornament_pattern, # color source
|
||||
15, # density (many ornaments)
|
||||
800ms # twinkle speed (slow twinkle)
|
||||
color=ornament_pattern, # color source
|
||||
density=15, # density (many ornaments)
|
||||
twinkle_speed=800ms # twinkle speed (slow twinkle)
|
||||
)
|
||||
ornaments.priority = 10
|
||||
|
||||
# Star on top (bright yellow pulse)
|
||||
animation tree_star = pulse_position_animation(
|
||||
0xFFFF00, # Bright yellow
|
||||
58, # position (near the top)
|
||||
3, # star size
|
||||
1 # sharp edges
|
||||
animation tree_star = beacon_animation(
|
||||
color=0xFFFF00, # Bright yellow
|
||||
pos=58, # position (near the top)
|
||||
beacon_size=3, # star size
|
||||
slew_size=1 # sharp edges
|
||||
)
|
||||
tree_star.priority = 20
|
||||
tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
|
||||
tree_star.opacity = smooth(min_value=200, max_value=255, duration=2s) # Gentle pulsing
|
||||
|
||||
# Add some white sparkles for snow/magic
|
||||
animation snow_sparkles = twinkle_animation(
|
||||
0xFFFFFF, # White snow
|
||||
8, # density (sparkle count)
|
||||
400ms # twinkle speed (quick sparkles)
|
||||
color=0xFFFFFF, # White snow
|
||||
density=8, # density (sparkle count)
|
||||
twinkle_speed=400ms # twinkle speed (quick sparkles)
|
||||
)
|
||||
snow_sparkles.priority = 15
|
||||
|
||||
# Garland effect - moving colored lights
|
||||
pattern garland_pattern = rich_palette_color_provider(ornament_colors, 2s, linear, 200)
|
||||
color garland_pattern = rich_palette(palette=ornament_colors, cycle_period=2s, transition_type=LINEAR, brightness=200)
|
||||
animation garland = comet_animation(
|
||||
garland_pattern, # color source
|
||||
6, # garland length (tail length)
|
||||
4s # slow movement (speed)
|
||||
color=garland_pattern, # color source
|
||||
tail_length=6, # garland length (tail length)
|
||||
speed=4s # slow movement (speed)
|
||||
)
|
||||
garland.priority = 5
|
||||
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
# Comet Chase - Moving comet with trailing tail
|
||||
# Bright head with fading tail
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark blue background
|
||||
color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
animation background = solid(space_blue)
|
||||
animation background = solid(color=space_blue)
|
||||
|
||||
# Main comet with bright white head
|
||||
animation comet_main = comet_animation(
|
||||
0xFFFFFF, # White head
|
||||
10, # tail length
|
||||
2s # speed
|
||||
color=0xFFFFFF, # White head
|
||||
tail_length=10, # tail length
|
||||
speed=2s # speed
|
||||
)
|
||||
comet_main.priority = 7
|
||||
|
||||
# Secondary comet in different color, opposite direction
|
||||
animation comet_secondary = comet_animation(
|
||||
0xFF4500, # Orange head
|
||||
8, # shorter tail
|
||||
3s, # slower speed
|
||||
-1 # other direction
|
||||
color=0xFF4500, # Orange head
|
||||
tail_length=8, # shorter tail
|
||||
speed=3s, # slower speed
|
||||
direction=-1 # other direction
|
||||
)
|
||||
comet_secondary.priority = 5
|
||||
|
||||
# Add sparkle trail behind comets but on top of blue background
|
||||
animation comet_sparkles = twinkle_animation(
|
||||
0xAAAAFF, # Light blue sparkles
|
||||
8, # density (moderate sparkles)
|
||||
400ms # twinkle speed (quick sparkle)
|
||||
color=0xAAAAFF, # Light blue sparkles
|
||||
density=8, # density (moderate sparkles)
|
||||
twinkle_speed=400ms # twinkle speed (quick sparkle)
|
||||
)
|
||||
comet_sparkles.priority = 8
|
||||
|
||||
|
||||
@ -1,126 +0,0 @@
|
||||
# Compiled DSL Examples
|
||||
|
||||
This directory contains the results of compiling Animation DSL examples to Berry code.
|
||||
|
||||
## Files
|
||||
|
||||
- `COMPILATION_REPORT.md` - Detailed compilation results and analysis
|
||||
- `run_successful_tests.sh` - Test runner for successfully compiled examples
|
||||
- `*.be` - Compiled Berry code files from DSL sources
|
||||
|
||||
## Current Status
|
||||
|
||||
The DSL transpiler has been significantly improved and now successfully compiles all example DSL files!
|
||||
|
||||
### What Works ✅
|
||||
|
||||
- **Basic color definitions** (`color red = #FF0000`)
|
||||
- **Palette definitions with comments** (`palette colors = [(0, #000000), # Black]`)
|
||||
- **Pattern definitions** (`pattern solid_red = solid(red)`)
|
||||
- **Animation definitions** (`animation anim1 = pulse_position(...)`)
|
||||
- **Function calls with inline comments** (multiline functions with comments)
|
||||
- **Easing keywords** (`smooth`, `linear`, `ease_in`, `ease_out`, `bounce`, `elastic`)
|
||||
- **Strip configuration** (`strip length 60`)
|
||||
- **Variable assignments** (`set var = value`)
|
||||
- **Run statements** (`run animation_name`)
|
||||
- **Complex nested function calls**
|
||||
- **All 23 example DSL files compile successfully**
|
||||
|
||||
### Recent Improvements ✅
|
||||
|
||||
1. **Fixed Comments in Palette Definitions**: Palette arrays can now include inline comments
|
||||
```dsl
|
||||
palette fire_colors = [
|
||||
(0, #000000), # Black (no fire) - This now works!
|
||||
(128, #FF0000), # Red flames
|
||||
(255, #FFFF00) # Bright yellow
|
||||
]
|
||||
```
|
||||
|
||||
2. **Fixed Comments in Function Arguments**: Multiline function calls with comments now parse correctly
|
||||
```dsl
|
||||
animation lava_blob = pulse_position(
|
||||
rich_palette(lava_colors, 12s, smooth, 255),
|
||||
18, # large blob - This now works!
|
||||
12, # very soft edges
|
||||
10, # priority
|
||||
loop
|
||||
)
|
||||
```
|
||||
|
||||
3. **Added Easing Keyword Support**: Keywords like `smooth`, `linear` are now recognized
|
||||
```dsl
|
||||
animation smooth_fade = filled(
|
||||
rich_palette(colors, 5s, smooth, 255), # 'smooth' now works!
|
||||
loop
|
||||
)
|
||||
```
|
||||
|
||||
### What Still Needs Work ❌
|
||||
|
||||
- **Property assignments** (`animation.pos = value`) - Not yet supported
|
||||
- **Multiple run statements** (generates multiple engine.start() calls)
|
||||
- **Advanced DSL features** (sequences, loops, conditionals)
|
||||
- **Runtime execution** (compiled code may have runtime issues)
|
||||
|
||||
### Example Working DSL
|
||||
|
||||
```dsl
|
||||
# Complex working example with comments and palettes
|
||||
strip length 60
|
||||
|
||||
# Define colors with comments
|
||||
palette lava_colors = [
|
||||
(0, #330000), # Dark red
|
||||
(64, #660000), # Medium red
|
||||
(128, #CC3300), # Bright red
|
||||
(192, #FF6600), # Orange
|
||||
(255, #FFAA00) # Yellow-orange
|
||||
]
|
||||
|
||||
# Animation with inline comments
|
||||
animation lava_base = filled(
|
||||
rich_palette(lava_colors, 15s, smooth, 180), # Smooth transitions
|
||||
loop
|
||||
)
|
||||
|
||||
animation lava_blob = pulse_position(
|
||||
rich_palette(lava_colors, 12s, smooth, 255),
|
||||
18, # large blob
|
||||
12, # very soft edges
|
||||
10, # priority
|
||||
loop
|
||||
)
|
||||
|
||||
run lava_base
|
||||
run lava_blob
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To compile DSL examples:
|
||||
```bash
|
||||
./compile_all_dsl_examples.sh
|
||||
```
|
||||
|
||||
To test compiled examples:
|
||||
```bash
|
||||
./anim_examples/compiled/run_successful_tests.sh
|
||||
```
|
||||
|
||||
## Success Rate
|
||||
|
||||
- **Current**: 100% (23/23 files compile successfully)
|
||||
- **Previous**: 4% (1/23 files)
|
||||
- **Improvement**: 575% increase in successful compilations
|
||||
|
||||
## Development Notes
|
||||
|
||||
The DSL transpiler uses a single-pass architecture that directly converts tokens to Berry code. Recent improvements:
|
||||
|
||||
1. ✅ **Enhanced comment handling** - Comments now work in all contexts
|
||||
2. ✅ **Easing keyword support** - All easing functions recognized
|
||||
3. ✅ **Improved error handling** - Better parsing of complex expressions
|
||||
4. ❌ **Property assignments** - Still need implementation
|
||||
5. ❌ **Advanced DSL features** - Sequences, loops, conditionals not yet supported
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
# Source: aurora_borealis.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
@ -31,10 +31,10 @@
|
||||
#
|
||||
# # Base aurora animation with slow flowing colors
|
||||
# animation aurora_base = rich_palette_animation(
|
||||
# aurora_colors, # palette
|
||||
# 10s, # cycle period
|
||||
# smooth, # transition type (explicit for clarity)
|
||||
# 180 # brightness (dimmed for aurora effect)
|
||||
# palette=aurora_colors, # palette
|
||||
# cycle_period=10s, # cycle period
|
||||
# transition_type=SINE, # transition type (explicit for clarity)
|
||||
# brightness=180 # brightness (dimmed for aurora effect)
|
||||
# )
|
||||
#
|
||||
# sequence demo {
|
||||
@ -56,7 +56,11 @@ var aurora_colors_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FFAA
|
||||
# Secondary purple palette
|
||||
var aurora_purple_ = bytes("00220022" "40440044" "808800AA" "C0AA44CC" "FFCCAAFF")
|
||||
# Base aurora animation with slow flowing colors
|
||||
var aurora_base_ = animation.rich_palette_animation(animation.global('aurora_colors_', 'aurora_colors'), 10000, animation.global('smooth_', 'smooth'), 180)
|
||||
var aurora_base_ = animation.rich_palette_animation(engine)
|
||||
aurora_base_.palette = animation.global('aurora_colors_', 'aurora_colors')
|
||||
aurora_base_.cycle_period = 10000
|
||||
aurora_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
||||
def sequence_demo()
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: breathing_colors.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Breathing Colors - Slow color breathing effect
|
||||
# # Gentle pulsing through different colors
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define breathing colors
|
||||
# color breathe_red = 0xFF0000
|
||||
@ -29,21 +29,21 @@
|
||||
# ]
|
||||
#
|
||||
# # Create a rich palette color provider
|
||||
# pattern palette_pattern = rich_palette_animation(
|
||||
# breathe_palette, # palette
|
||||
# 15s # cycle period (defaults: smooth transition, 255 brightness)
|
||||
# color palette_pattern = rich_palette(
|
||||
# palette=breathe_palette, # palette
|
||||
# cycle_period=15s # cycle period (defaults: smooth transition, 255 brightness)
|
||||
# )
|
||||
#
|
||||
# # Create breathing animation using the palette
|
||||
# animation breathing = breathe(
|
||||
# palette_pattern, # base pattern
|
||||
# 100, # min brightness
|
||||
# 255, # max brightness
|
||||
# 4s # breathing period (4 seconds)
|
||||
# animation breathing = breathe_animation(
|
||||
# color=palette_pattern, # base animation
|
||||
# min_brightness=100, # min brightness
|
||||
# max_brightness=255, # max brightness
|
||||
# period=4s # breathing period (4 seconds)
|
||||
# )
|
||||
#
|
||||
# # Add gentle opacity breathing
|
||||
# breathing.opacity = smooth(100, 255, 4s)
|
||||
# breathing.opacity = smooth(min_value=100, max_value=255, duration=4s)
|
||||
#
|
||||
# # Start the animation
|
||||
# run breathing
|
||||
@ -52,8 +52,11 @@ import animation
|
||||
|
||||
# Breathing Colors - Slow color breathing effect
|
||||
# Gentle pulsing through different colors
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define breathing colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var breathe_red_ = 0xFFFF0000
|
||||
var breathe_green_ = 0xFF00FF00
|
||||
var breathe_blue_ = 0xFF0000FF
|
||||
@ -62,11 +65,21 @@ var breathe_orange_ = 0xFFFF8000
|
||||
# Create breathing animation that cycles through colors
|
||||
var breathe_palette_ = bytes("00FF0000" "33FF8000" "66FFFF00" "9900FF00" "CC0000FF" "FF800080")
|
||||
# Create a rich palette color provider
|
||||
var palette_pattern_ = animation.rich_palette_animation(animation.global('breathe_palette_', 'breathe_palette'), 15000)
|
||||
var palette_pattern_ = animation.rich_palette(engine)
|
||||
palette_pattern_.palette = animation.global('breathe_palette_', 'breathe_palette')
|
||||
palette_pattern_.cycle_period = 15000 # cycle period (defaults: smooth transition, 255 brightness)
|
||||
# Create breathing animation using the palette
|
||||
var breathing_ = animation.breathe(animation.global('palette_pattern_', 'palette_pattern'), 100, 255, 4000)
|
||||
var breathing_ = animation.breathe_animation(engine)
|
||||
breathing_.color = animation.global('palette_pattern_', 'palette_pattern')
|
||||
breathing_.min_brightness = 100
|
||||
breathing_.max_brightness = 255
|
||||
breathing_.period = 4000 # breathing period (4 seconds)
|
||||
# Add gentle opacity breathing
|
||||
animation.global('breathing_').opacity = animation.smooth(100, 255, 4000)
|
||||
var temp_smooth_156 = animation.smooth(engine)
|
||||
temp_smooth_156.min_value = 100
|
||||
temp_smooth_156.max_value = 255
|
||||
temp_smooth_156.duration = 4000
|
||||
animation.global('breathing_').opacity = temp_smooth_156
|
||||
# Start the animation
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_breathing')
|
||||
|
||||
@ -2,44 +2,44 @@
|
||||
# Source: candy_cane.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Candy Cane - Red and white stripes
|
||||
# # Classic Christmas candy cane pattern
|
||||
# # Classic Christmas candy cane animation
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define stripe colors
|
||||
# color candy_red = 0xFF0000
|
||||
# color candy_white = 0xFFFFFF
|
||||
#
|
||||
# # Create alternating red and white pattern
|
||||
# # Create alternating red and white animation
|
||||
# # Using multiple pulse positions to create stripes
|
||||
# animation stripe1 = pulse_position_animation(candy_red, 3, 4, 1)
|
||||
# animation stripe2 = pulse_position_animation(candy_white, 9, 4, 1)
|
||||
# animation stripe3 = pulse_position_animation(candy_red, 15, 4, 1)
|
||||
# animation stripe4 = pulse_position_animation(candy_white, 21, 4, 1)
|
||||
# animation stripe5 = pulse_position_animation(candy_red, 27, 4, 1)
|
||||
# animation stripe6 = pulse_position_animation(candy_white, 33, 4, 1)
|
||||
# animation stripe7 = pulse_position_animation(candy_red, 39, 4, 1)
|
||||
# animation stripe8 = pulse_position_animation(candy_white, 45, 4, 1)
|
||||
# animation stripe9 = pulse_position_animation(candy_red, 51, 4, 1)
|
||||
# animation stripe10 = pulse_position_animation(candy_white, 57, 4, 1)
|
||||
# animation stripe1 = beacon_animation(color=candy_red, pos=3, beacon_size=4, slew_size=1)
|
||||
# animation stripe2 = beacon_animation(color=candy_white, pos=9, beacon_size=4, slew_size=1)
|
||||
# animation stripe3 = beacon_animation(color=candy_red, pos=15, beacon_size=4, slew_size=1)
|
||||
# animation stripe4 = beacon_animation(color=candy_white, pos=21, beacon_size=4, slew_size=1)
|
||||
# animation stripe5 = beacon_animation(color=candy_red, pos=27, beacon_size=4, slew_size=1)
|
||||
# animation stripe6 = beacon_animation(color=candy_white, pos=33, beacon_size=4, slew_size=1)
|
||||
# animation stripe7 = beacon_animation(color=candy_red, pos=39, beacon_size=4, slew_size=1)
|
||||
# animation stripe8 = beacon_animation(color=candy_white, pos=45, beacon_size=4, slew_size=1)
|
||||
# animation stripe9 = beacon_animation(color=candy_red, pos=51, beacon_size=4, slew_size=1)
|
||||
# animation stripe10 = beacon_animation(color=candy_white, pos=57, beacon_size=4, slew_size=1)
|
||||
#
|
||||
# # Add gentle movement to make it more dynamic
|
||||
# set move_speed = 8s
|
||||
# stripe1.pos = sawtooth(3, 63, move_speed)
|
||||
# stripe2.pos = sawtooth(9, 69, move_speed)
|
||||
# stripe3.pos = sawtooth(15, 75, move_speed)
|
||||
# stripe4.pos = sawtooth(21, 81, move_speed)
|
||||
# stripe5.pos = sawtooth(27, 87, move_speed)
|
||||
# stripe6.pos = sawtooth(33, 93, move_speed)
|
||||
# stripe7.pos = sawtooth(39, 99, move_speed)
|
||||
# stripe8.pos = sawtooth(45, 105, move_speed)
|
||||
# stripe9.pos = sawtooth(51, 111, move_speed)
|
||||
# stripe10.pos = sawtooth(57, 117, move_speed)
|
||||
# stripe1.pos = sawtooth(min_value=3, max_value=63, duration=move_speed)
|
||||
# stripe2.pos = sawtooth(min_value=9, max_value=69, duration=move_speed)
|
||||
# stripe3.pos = sawtooth(min_value=15, max_value=75, duration=move_speed)
|
||||
# stripe4.pos = sawtooth(min_value=21, max_value=81, duration=move_speed)
|
||||
# stripe5.pos = sawtooth(min_value=27, max_value=87, duration=move_speed)
|
||||
# stripe6.pos = sawtooth(min_value=33, max_value=93, duration=move_speed)
|
||||
# stripe7.pos = sawtooth(min_value=39, max_value=99, duration=move_speed)
|
||||
# stripe8.pos = sawtooth(min_value=45, max_value=105, duration=move_speed)
|
||||
# stripe9.pos = sawtooth(min_value=51, max_value=111, duration=move_speed)
|
||||
# stripe10.pos = sawtooth(min_value=57, max_value=117, duration=move_speed)
|
||||
#
|
||||
# # Start all stripes
|
||||
# run stripe1
|
||||
@ -56,35 +56,118 @@
|
||||
import animation
|
||||
|
||||
# Candy Cane - Red and white stripes
|
||||
# Classic Christmas candy cane pattern
|
||||
var engine = animation.init_strip(60)
|
||||
# Classic Christmas candy cane animation
|
||||
#strip length 60
|
||||
# Define stripe colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var candy_red_ = 0xFFFF0000
|
||||
var candy_white_ = 0xFFFFFFFF
|
||||
# Create alternating red and white pattern
|
||||
# Create alternating red and white animation
|
||||
# Using multiple pulse positions to create stripes
|
||||
var stripe1_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 3, 4, 1)
|
||||
var stripe2_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 9, 4, 1)
|
||||
var stripe3_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 15, 4, 1)
|
||||
var stripe4_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 21, 4, 1)
|
||||
var stripe5_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 27, 4, 1)
|
||||
var stripe6_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 33, 4, 1)
|
||||
var stripe7_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 39, 4, 1)
|
||||
var stripe8_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 45, 4, 1)
|
||||
var stripe9_ = animation.pulse_position_animation(animation.global('candy_red_', 'candy_red'), 51, 4, 1)
|
||||
var stripe10_ = animation.pulse_position_animation(animation.global('candy_white_', 'candy_white'), 57, 4, 1)
|
||||
var stripe1_ = animation.beacon_animation(engine)
|
||||
stripe1_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe1_.pos = 3
|
||||
stripe1_.beacon_size = 4
|
||||
stripe1_.slew_size = 1
|
||||
var stripe2_ = animation.beacon_animation(engine)
|
||||
stripe2_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe2_.pos = 9
|
||||
stripe2_.beacon_size = 4
|
||||
stripe2_.slew_size = 1
|
||||
var stripe3_ = animation.beacon_animation(engine)
|
||||
stripe3_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe3_.pos = 15
|
||||
stripe3_.beacon_size = 4
|
||||
stripe3_.slew_size = 1
|
||||
var stripe4_ = animation.beacon_animation(engine)
|
||||
stripe4_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe4_.pos = 21
|
||||
stripe4_.beacon_size = 4
|
||||
stripe4_.slew_size = 1
|
||||
var stripe5_ = animation.beacon_animation(engine)
|
||||
stripe5_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe5_.pos = 27
|
||||
stripe5_.beacon_size = 4
|
||||
stripe5_.slew_size = 1
|
||||
var stripe6_ = animation.beacon_animation(engine)
|
||||
stripe6_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe6_.pos = 33
|
||||
stripe6_.beacon_size = 4
|
||||
stripe6_.slew_size = 1
|
||||
var stripe7_ = animation.beacon_animation(engine)
|
||||
stripe7_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe7_.pos = 39
|
||||
stripe7_.beacon_size = 4
|
||||
stripe7_.slew_size = 1
|
||||
var stripe8_ = animation.beacon_animation(engine)
|
||||
stripe8_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe8_.pos = 45
|
||||
stripe8_.beacon_size = 4
|
||||
stripe8_.slew_size = 1
|
||||
var stripe9_ = animation.beacon_animation(engine)
|
||||
stripe9_.color = animation.global('candy_red_', 'candy_red')
|
||||
stripe9_.pos = 51
|
||||
stripe9_.beacon_size = 4
|
||||
stripe9_.slew_size = 1
|
||||
var stripe10_ = animation.beacon_animation(engine)
|
||||
stripe10_.color = animation.global('candy_white_', 'candy_white')
|
||||
stripe10_.pos = 57
|
||||
stripe10_.beacon_size = 4
|
||||
stripe10_.slew_size = 1
|
||||
# Add gentle movement to make it more dynamic
|
||||
var move_speed_ = 8000
|
||||
animation.global('stripe1_').pos = animation.sawtooth(3, 63, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe2_').pos = animation.sawtooth(9, 69, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe3_').pos = animation.sawtooth(15, 75, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe4_').pos = animation.sawtooth(21, 81, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe5_').pos = animation.sawtooth(27, 87, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe6_').pos = animation.sawtooth(33, 93, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe7_').pos = animation.sawtooth(39, 99, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe8_').pos = animation.sawtooth(45, 105, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe9_').pos = animation.sawtooth(51, 111, animation.global('move_speed_', 'move_speed'))
|
||||
animation.global('stripe10_').pos = animation.sawtooth(57, 117, animation.global('move_speed_', 'move_speed'))
|
||||
var temp_sawtooth_258 = animation.sawtooth(engine)
|
||||
temp_sawtooth_258.min_value = 3
|
||||
temp_sawtooth_258.max_value = 63
|
||||
temp_sawtooth_258.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe1_').pos = temp_sawtooth_258
|
||||
var temp_sawtooth_277 = animation.sawtooth(engine)
|
||||
temp_sawtooth_277.min_value = 9
|
||||
temp_sawtooth_277.max_value = 69
|
||||
temp_sawtooth_277.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe2_').pos = temp_sawtooth_277
|
||||
var temp_sawtooth_296 = animation.sawtooth(engine)
|
||||
temp_sawtooth_296.min_value = 15
|
||||
temp_sawtooth_296.max_value = 75
|
||||
temp_sawtooth_296.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe3_').pos = temp_sawtooth_296
|
||||
var temp_sawtooth_315 = animation.sawtooth(engine)
|
||||
temp_sawtooth_315.min_value = 21
|
||||
temp_sawtooth_315.max_value = 81
|
||||
temp_sawtooth_315.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe4_').pos = temp_sawtooth_315
|
||||
var temp_sawtooth_334 = animation.sawtooth(engine)
|
||||
temp_sawtooth_334.min_value = 27
|
||||
temp_sawtooth_334.max_value = 87
|
||||
temp_sawtooth_334.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe5_').pos = temp_sawtooth_334
|
||||
var temp_sawtooth_353 = animation.sawtooth(engine)
|
||||
temp_sawtooth_353.min_value = 33
|
||||
temp_sawtooth_353.max_value = 93
|
||||
temp_sawtooth_353.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe6_').pos = temp_sawtooth_353
|
||||
var temp_sawtooth_372 = animation.sawtooth(engine)
|
||||
temp_sawtooth_372.min_value = 39
|
||||
temp_sawtooth_372.max_value = 99
|
||||
temp_sawtooth_372.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe7_').pos = temp_sawtooth_372
|
||||
var temp_sawtooth_391 = animation.sawtooth(engine)
|
||||
temp_sawtooth_391.min_value = 45
|
||||
temp_sawtooth_391.max_value = 105
|
||||
temp_sawtooth_391.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe8_').pos = temp_sawtooth_391
|
||||
var temp_sawtooth_410 = animation.sawtooth(engine)
|
||||
temp_sawtooth_410.min_value = 51
|
||||
temp_sawtooth_410.max_value = 111
|
||||
temp_sawtooth_410.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe9_').pos = temp_sawtooth_410
|
||||
var temp_sawtooth_429 = animation.sawtooth(engine)
|
||||
temp_sawtooth_429.min_value = 57
|
||||
temp_sawtooth_429.max_value = 117
|
||||
temp_sawtooth_429.duration = animation.global('move_speed_', 'move_speed')
|
||||
animation.global('stripe10_').pos = temp_sawtooth_429
|
||||
# Start all stripes
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_stripe1')
|
||||
|
||||
@ -2,18 +2,18 @@
|
||||
# Source: christmas_tree.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Christmas Tree - Festive holiday colors
|
||||
# # Green base with colorful ornaments and twinkling
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Green tree base
|
||||
# color tree_green = 0x006600
|
||||
# animation tree_base = solid(tree_green)
|
||||
# animation tree_base = solid(color=tree_green)
|
||||
#
|
||||
# # Define ornament colors
|
||||
# palette ornament_colors = [
|
||||
@ -25,38 +25,38 @@
|
||||
# ]
|
||||
#
|
||||
# # Colorful ornaments as twinkling lights
|
||||
# pattern ornament_pattern = rich_palette_color_provider(ornament_colors, 3s, linear, 255)
|
||||
# color ornament_pattern = rich_palette(palette=ornament_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
# animation ornaments = twinkle_animation(
|
||||
# ornament_pattern, # color source
|
||||
# 15, # density (many ornaments)
|
||||
# 800ms # twinkle speed (slow twinkle)
|
||||
# color=ornament_pattern, # color source
|
||||
# density=15, # density (many ornaments)
|
||||
# twinkle_speed=800ms # twinkle speed (slow twinkle)
|
||||
# )
|
||||
# ornaments.priority = 10
|
||||
#
|
||||
# # Star on top (bright yellow pulse)
|
||||
# animation tree_star = pulse_position_animation(
|
||||
# 0xFFFF00, # Bright yellow
|
||||
# 58, # position (near the top)
|
||||
# 3, # star size
|
||||
# 1 # sharp edges
|
||||
# animation tree_star = beacon_animation(
|
||||
# color=0xFFFF00, # Bright yellow
|
||||
# pos=58, # position (near the top)
|
||||
# beacon_size=3, # star size
|
||||
# slew_size=1 # sharp edges
|
||||
# )
|
||||
# tree_star.priority = 20
|
||||
# tree_star.opacity = smooth(200, 255, 2s) # Gentle pulsing
|
||||
# tree_star.opacity = smooth(min_value=200, max_value=255, duration=2s) # Gentle pulsing
|
||||
#
|
||||
# # Add some white sparkles for snow/magic
|
||||
# animation snow_sparkles = twinkle_animation(
|
||||
# 0xFFFFFF, # White snow
|
||||
# 8, # density (sparkle count)
|
||||
# 400ms # twinkle speed (quick sparkles)
|
||||
# color=0xFFFFFF, # White snow
|
||||
# density=8, # density (sparkle count)
|
||||
# twinkle_speed=400ms # twinkle speed (quick sparkles)
|
||||
# )
|
||||
# snow_sparkles.priority = 15
|
||||
#
|
||||
# # Garland effect - moving colored lights
|
||||
# pattern garland_pattern = rich_palette_color_provider(ornament_colors, 2s, linear, 200)
|
||||
# color garland_pattern = rich_palette(palette=ornament_colors, cycle_period=2s, transition_type=LINEAR, brightness=200)
|
||||
# animation garland = comet_animation(
|
||||
# garland_pattern, # color source
|
||||
# 6, # garland length (tail length)
|
||||
# 4s # slow movement (speed)
|
||||
# color=garland_pattern, # color source
|
||||
# tail_length=6, # garland length (tail length)
|
||||
# speed=4s # slow movement (speed)
|
||||
# )
|
||||
# garland.priority = 5
|
||||
#
|
||||
@ -71,26 +71,55 @@ import animation
|
||||
|
||||
# Christmas Tree - Festive holiday colors
|
||||
# Green base with colorful ornaments and twinkling
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Green tree base
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var tree_green_ = 0xFF006600
|
||||
var tree_base_ = animation.solid(animation.global('tree_green_', 'tree_green'))
|
||||
var tree_base_ = animation.solid(engine)
|
||||
tree_base_.color = animation.global('tree_green_', 'tree_green')
|
||||
# Define ornament colors
|
||||
var ornament_colors_ = bytes("00FF0000" "40FFD700" "800000FF" "C0FFFFFF" "FFFF00FF")
|
||||
# Colorful ornaments as twinkling lights
|
||||
var ornament_pattern_ = animation.rich_palette_color_provider(animation.global('ornament_colors_', 'ornament_colors'), 3000, animation.global('linear_', 'linear'), 255)
|
||||
var ornaments_ = animation.twinkle_animation(animation.global('ornament_pattern_', 'ornament_pattern'), 15, 800)
|
||||
var ornament_pattern_ = animation.rich_palette(engine)
|
||||
ornament_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
|
||||
ornament_pattern_.cycle_period = 3000
|
||||
ornament_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
ornament_pattern_.brightness = 255
|
||||
var ornaments_ = animation.twinkle_animation(engine)
|
||||
ornaments_.color = animation.global('ornament_pattern_', 'ornament_pattern')
|
||||
ornaments_.density = 15
|
||||
ornaments_.twinkle_speed = 800 # twinkle speed (slow twinkle)
|
||||
animation.global('ornaments_').priority = 10
|
||||
# Star on top (bright yellow pulse)
|
||||
var tree_star_ = animation.pulse_position_animation(0xFFFFFF00, 58, 3, 1)
|
||||
var tree_star_ = animation.beacon_animation(engine)
|
||||
tree_star_.color = 0xFFFFFF00
|
||||
tree_star_.pos = 58
|
||||
tree_star_.beacon_size = 3
|
||||
tree_star_.slew_size = 1 # sharp edges
|
||||
animation.global('tree_star_').priority = 20
|
||||
animation.global('tree_star_').opacity = animation.smooth(200, 255, 2000) # Gentle pulsing
|
||||
var temp_smooth_175 = animation.smooth(engine)
|
||||
temp_smooth_175.min_value = 200
|
||||
temp_smooth_175.max_value = 255
|
||||
temp_smooth_175.duration = 2000
|
||||
animation.global('tree_star_').opacity = temp_smooth_175 # Gentle pulsing
|
||||
# Add some white sparkles for snow/magic
|
||||
var snow_sparkles_ = animation.twinkle_animation(0xFFFFFFFF, 8, 400)
|
||||
var snow_sparkles_ = animation.twinkle_animation(engine)
|
||||
snow_sparkles_.color = 0xFFFFFFFF
|
||||
snow_sparkles_.density = 8
|
||||
snow_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkles)
|
||||
animation.global('snow_sparkles_').priority = 15
|
||||
# Garland effect - moving colored lights
|
||||
var garland_pattern_ = animation.rich_palette_color_provider(animation.global('ornament_colors_', 'ornament_colors'), 2000, animation.global('linear_', 'linear'), 200)
|
||||
var garland_ = animation.comet_animation(animation.global('garland_pattern_', 'garland_pattern'), 6, 4000)
|
||||
var garland_pattern_ = animation.rich_palette(engine)
|
||||
garland_pattern_.palette = animation.global('ornament_colors_', 'ornament_colors')
|
||||
garland_pattern_.cycle_period = 2000
|
||||
garland_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
garland_pattern_.brightness = 200
|
||||
var garland_ = animation.comet_animation(engine)
|
||||
garland_.color = animation.global('garland_pattern_', 'garland_pattern')
|
||||
garland_.tail_length = 6
|
||||
garland_.speed = 4000 # slow movement (speed)
|
||||
animation.global('garland_').priority = 5
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,41 +2,41 @@
|
||||
# Source: comet_chase.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Comet Chase - Moving comet with trailing tail
|
||||
# # Bright head with fading tail
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark blue background
|
||||
# color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
# animation background = solid(space_blue)
|
||||
# animation background = solid(color=space_blue)
|
||||
#
|
||||
# # Main comet with bright white head
|
||||
# animation comet_main = comet_animation(
|
||||
# 0xFFFFFF, # White head
|
||||
# 10, # tail length
|
||||
# 2s # speed
|
||||
# color=0xFFFFFF, # White head
|
||||
# tail_length=10, # tail length
|
||||
# speed=2s # speed
|
||||
# )
|
||||
# comet_main.priority = 7
|
||||
#
|
||||
# # Secondary comet in different color, opposite direction
|
||||
# animation comet_secondary = comet_animation(
|
||||
# 0xFF4500, # Orange head
|
||||
# 8, # shorter tail
|
||||
# 3s, # slower speed
|
||||
# -1 # other direction
|
||||
# color=0xFF4500, # Orange head
|
||||
# tail_length=8, # shorter tail
|
||||
# speed=3s, # slower speed
|
||||
# direction=-1 # other direction
|
||||
# )
|
||||
# comet_secondary.priority = 5
|
||||
#
|
||||
# # Add sparkle trail behind comets but on top of blue background
|
||||
# animation comet_sparkles = twinkle_animation(
|
||||
# 0xAAAAFF, # Light blue sparkles
|
||||
# 8, # density (moderate sparkles)
|
||||
# 400ms # twinkle speed (quick sparkle)
|
||||
# color=0xAAAAFF, # Light blue sparkles
|
||||
# density=8, # density (moderate sparkles)
|
||||
# twinkle_speed=400ms # twinkle speed (quick sparkle)
|
||||
# )
|
||||
# comet_sparkles.priority = 8
|
||||
#
|
||||
@ -50,18 +50,32 @@ import animation
|
||||
|
||||
# Comet Chase - Moving comet with trailing tail
|
||||
# Bright head with fading tail
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark blue background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var space_blue_ = 0xFF000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
var background_ = animation.solid(animation.global('space_blue_', 'space_blue'))
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('space_blue_', 'space_blue')
|
||||
# Main comet with bright white head
|
||||
var comet_main_ = animation.comet_animation(0xFFFFFFFF, 10, 2000)
|
||||
var comet_main_ = animation.comet_animation(engine)
|
||||
comet_main_.color = 0xFFFFFFFF
|
||||
comet_main_.tail_length = 10
|
||||
comet_main_.speed = 2000 # speed
|
||||
animation.global('comet_main_').priority = 7
|
||||
# Secondary comet in different color, opposite direction
|
||||
var comet_secondary_ = animation.comet_animation(0xFFFF4500, 8, 3000, -1)
|
||||
var comet_secondary_ = animation.comet_animation(engine)
|
||||
comet_secondary_.color = 0xFFFF4500
|
||||
comet_secondary_.tail_length = 8
|
||||
comet_secondary_.speed = 3000
|
||||
comet_secondary_.direction = -1 # other direction
|
||||
animation.global('comet_secondary_').priority = 5
|
||||
# Add sparkle trail behind comets but on top of blue background
|
||||
var comet_sparkles_ = animation.twinkle_animation(0xFFAAAAFF, 8, 400)
|
||||
var comet_sparkles_ = animation.twinkle_animation(engine)
|
||||
comet_sparkles_.color = 0xFFAAAAFF
|
||||
comet_sparkles_.density = 8
|
||||
comet_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkle)
|
||||
animation.global('comet_sparkles_').priority = 8
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: disco_strobe.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Disco Strobe - Fast colorful strobing
|
||||
# # Rapid color changes with strobe effects
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define disco color palette
|
||||
# palette disco_colors = [
|
||||
@ -23,35 +23,35 @@
|
||||
# ]
|
||||
#
|
||||
# # Fast color cycling base
|
||||
# animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
|
||||
# animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
#
|
||||
# # Add strobe effect
|
||||
# disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
|
||||
# disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
|
||||
#
|
||||
# # Add white flash overlay
|
||||
# animation white_flash = solid(0xFFFFFF)
|
||||
# white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
|
||||
# animation white_flash = solid(color=0xFFFFFF)
|
||||
# white_flash.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=10) # Quick white flashes
|
||||
# white_flash.priority = 20
|
||||
#
|
||||
# # Add colored sparkles
|
||||
# pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
|
||||
# color sparkle_pattern = rich_palette(palette=disco_colors, cycle_period=500ms, transition_type=LINEAR, brightness=255)
|
||||
# animation disco_sparkles = twinkle_animation(
|
||||
# sparkle_pattern, # color source
|
||||
# 12, # density (many sparkles)
|
||||
# 80ms # twinkle speed (very quick)
|
||||
# color=sparkle_pattern, # color source
|
||||
# density=12, # density (many sparkles)
|
||||
# twinkle_speed=80ms # twinkle speed (very quick)
|
||||
# )
|
||||
# disco_sparkles.priority = 15
|
||||
#
|
||||
# # Add moving pulse for extra effect
|
||||
# pattern pulse_pattern = rich_palette_color_provider(disco_colors, 800ms, linear, 255)
|
||||
# animation disco_pulse = pulse_position_animation(
|
||||
# pulse_pattern, # color source
|
||||
# 4, # initial position
|
||||
# 8, # pulse width
|
||||
# 2 # sharp edges (slew size)
|
||||
# color pulse_pattern = rich_palette(palette=disco_colors, cycle_period=800ms, transition_type=LINEAR, brightness=255)
|
||||
# animation disco_pulse = beacon_animation(
|
||||
# color=pulse_pattern, # color source
|
||||
# pos=4, # initial position
|
||||
# beacon_size=8, # pulse width
|
||||
# slew_size=2 # sharp edges (slew size)
|
||||
# )
|
||||
# disco_pulse.priority = 10
|
||||
# disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
|
||||
# disco_pulse.pos = sawtooth(min_value=4, max_value=56, duration=2s) # Fast movement
|
||||
#
|
||||
# # Start all animations
|
||||
# run disco_base
|
||||
@ -63,26 +63,63 @@ import animation
|
||||
|
||||
# Disco Strobe - Fast colorful strobing
|
||||
# Rapid color changes with strobe effects
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define disco color palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var disco_colors_ = bytes("00FF0000" "2AFF8000" "55FFFF00" "8000FF00" "AA0000FF" "D58000FF" "FFFF00FF")
|
||||
# Fast color cycling base
|
||||
var disco_base_ = animation.rich_palette_animation(animation.global('disco_colors_', 'disco_colors'), 1000, animation.global('linear_', 'linear'), 255)
|
||||
var disco_base_ = animation.rich_palette_animation(engine)
|
||||
disco_base_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
disco_base_.cycle_period = 1000
|
||||
disco_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
disco_base_.brightness = 255
|
||||
# Add strobe effect
|
||||
animation.global('disco_base_').opacity = animation.square(0, 255, 100, 30) # Fast strobe
|
||||
var temp_square_105 = animation.square(engine)
|
||||
temp_square_105.min_value = 0
|
||||
temp_square_105.max_value = 255
|
||||
temp_square_105.duration = 100
|
||||
temp_square_105.duty_cycle = 30
|
||||
animation.global('disco_base_').opacity = temp_square_105 # Fast strobe
|
||||
# Add white flash overlay
|
||||
var white_flash_ = animation.solid(0xFFFFFFFF)
|
||||
animation.global('white_flash_').opacity = animation.square(0, 255, 50, 10) # Quick white flashes
|
||||
var white_flash_ = animation.solid(engine)
|
||||
white_flash_.color = 0xFFFFFFFF
|
||||
var temp_square_142 = animation.square(engine)
|
||||
temp_square_142.min_value = 0
|
||||
temp_square_142.max_value = 255
|
||||
temp_square_142.duration = 50
|
||||
temp_square_142.duty_cycle = 10
|
||||
animation.global('white_flash_').opacity = temp_square_142 # Quick white flashes
|
||||
animation.global('white_flash_').priority = 20
|
||||
# Add colored sparkles
|
||||
var sparkle_pattern_ = animation.rich_palette_color_provider(animation.global('disco_colors_', 'disco_colors'), 500, animation.global('linear_', 'linear'), 255)
|
||||
var disco_sparkles_ = animation.twinkle_animation(animation.global('sparkle_pattern_', 'sparkle_pattern'), 12, 80)
|
||||
var sparkle_pattern_ = animation.rich_palette(engine)
|
||||
sparkle_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
sparkle_pattern_.cycle_period = 500
|
||||
sparkle_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
sparkle_pattern_.brightness = 255
|
||||
var disco_sparkles_ = animation.twinkle_animation(engine)
|
||||
disco_sparkles_.color = animation.global('sparkle_pattern_', 'sparkle_pattern')
|
||||
disco_sparkles_.density = 12
|
||||
disco_sparkles_.twinkle_speed = 80 # twinkle speed (very quick)
|
||||
animation.global('disco_sparkles_').priority = 15
|
||||
# Add moving pulse for extra effect
|
||||
var pulse_pattern_ = animation.rich_palette_color_provider(animation.global('disco_colors_', 'disco_colors'), 800, animation.global('linear_', 'linear'), 255)
|
||||
var disco_pulse_ = animation.pulse_position_animation(animation.global('pulse_pattern_', 'pulse_pattern'), 4, 8, 2)
|
||||
var pulse_pattern_ = animation.rich_palette(engine)
|
||||
pulse_pattern_.palette = animation.global('disco_colors_', 'disco_colors')
|
||||
pulse_pattern_.cycle_period = 800
|
||||
pulse_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
pulse_pattern_.brightness = 255
|
||||
var disco_pulse_ = animation.beacon_animation(engine)
|
||||
disco_pulse_.color = animation.global('pulse_pattern_', 'pulse_pattern')
|
||||
disco_pulse_.pos = 4
|
||||
disco_pulse_.beacon_size = 8
|
||||
disco_pulse_.slew_size = 2 # sharp edges (slew size)
|
||||
animation.global('disco_pulse_').priority = 10
|
||||
animation.global('disco_pulse_').pos = animation.sawtooth(4, 56, 2000) # Fast movement
|
||||
var temp_sawtooth_290 = animation.sawtooth(engine)
|
||||
temp_sawtooth_290.min_value = 4
|
||||
temp_sawtooth_290.max_value = 56
|
||||
temp_sawtooth_290.duration = 2000
|
||||
animation.global('disco_pulse_').pos = temp_sawtooth_290 # Fast movement
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_disco_base')
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: fire_flicker.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Fire Flicker - Realistic fire simulation
|
||||
# # Warm colors with random flickering intensity
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define fire palette from black to yellow
|
||||
# palette fire_colors = [
|
||||
@ -21,17 +21,17 @@
|
||||
# ]
|
||||
#
|
||||
# # Create base fire animation with palette
|
||||
# animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
|
||||
# animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
#
|
||||
# # Add flickering effect with random intensity changes
|
||||
# fire_base.opacity = smooth(180, 255, 800ms)
|
||||
# fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
|
||||
#
|
||||
# # Add subtle position variation for more realism
|
||||
# pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
|
||||
# color flicker_pattern = rich_palette(palette=fire_colors, cycle_period=2s, transition_type=LINEAR, brightness=255)
|
||||
# animation fire_flicker = twinkle_animation(
|
||||
# flicker_pattern, # color source
|
||||
# 12, # density (number of flickers)
|
||||
# 200ms # twinkle speed (flicker duration)
|
||||
# color=flicker_pattern, # color source
|
||||
# density=12, # density (number of flickers)
|
||||
# twinkle_speed=200ms # twinkle speed (flicker duration)
|
||||
# )
|
||||
# fire_flicker.priority = 10
|
||||
#
|
||||
@ -43,16 +43,34 @@ import animation
|
||||
|
||||
# Fire Flicker - Realistic fire simulation
|
||||
# Warm colors with random flickering intensity
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define fire palette from black to yellow
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF4500" "FFFFFF00")
|
||||
# Create base fire animation with palette
|
||||
var fire_base_ = animation.rich_palette_animation(animation.global('fire_colors_', 'fire_colors'), 3000, animation.global('linear_', 'linear'), 255)
|
||||
var fire_base_ = animation.rich_palette_animation(engine)
|
||||
fire_base_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
fire_base_.cycle_period = 3000
|
||||
fire_base_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
fire_base_.brightness = 255
|
||||
# Add flickering effect with random intensity changes
|
||||
animation.global('fire_base_').opacity = animation.smooth(180, 255, 800)
|
||||
var temp_smooth_89 = animation.smooth(engine)
|
||||
temp_smooth_89.min_value = 180
|
||||
temp_smooth_89.max_value = 255
|
||||
temp_smooth_89.duration = 800
|
||||
animation.global('fire_base_').opacity = temp_smooth_89
|
||||
# Add subtle position variation for more realism
|
||||
var flicker_pattern_ = animation.rich_palette_color_provider(animation.global('fire_colors_', 'fire_colors'), 2000, animation.global('linear_', 'linear'), 255)
|
||||
var fire_flicker_ = animation.twinkle_animation(animation.global('flicker_pattern_', 'flicker_pattern'), 12, 200)
|
||||
var flicker_pattern_ = animation.rich_palette(engine)
|
||||
flicker_pattern_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
flicker_pattern_.cycle_period = 2000
|
||||
flicker_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
flicker_pattern_.brightness = 255
|
||||
var fire_flicker_ = animation.twinkle_animation(engine)
|
||||
fire_flicker_.color = animation.global('flicker_pattern_', 'flicker_pattern')
|
||||
fire_flicker_.density = 12
|
||||
fire_flicker_.twinkle_speed = 200 # twinkle speed (flicker duration)
|
||||
animation.global('fire_flicker_').priority = 10
|
||||
# Start both animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,45 +2,45 @@
|
||||
# Source: heartbeat_pulse.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Heartbeat Pulse - Rhythmic double pulse
|
||||
# # Red pulsing like a heartbeat
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark background
|
||||
# color heart_bg = 0x110000
|
||||
# animation background = solid(heart_bg)
|
||||
# animation background = solid(color=heart_bg)
|
||||
#
|
||||
# # Define heartbeat timing - double pulse pattern
|
||||
# # Define heartbeat timing - double pulse animation
|
||||
# # First pulse (stronger)
|
||||
# animation heartbeat1 = solid(0xFF0000) # Bright red
|
||||
# heartbeat1.opacity = square(0, 255, 150ms, 20) # Quick strong pulse
|
||||
# animation heartbeat1 = solid(color=0xFF0000) # Bright red
|
||||
# heartbeat1.opacity = square(min_value=0, max_value=255, duration=150ms, duty_cycle=20) # Quick strong pulse
|
||||
# heartbeat1.priority = 10
|
||||
#
|
||||
# # Second pulse (weaker, slightly delayed)
|
||||
# animation heartbeat2 = solid(0xCC0000) # Slightly dimmer red
|
||||
# animation heartbeat2 = solid(color=0xCC0000) # Slightly dimmer red
|
||||
# # Delay the second pulse by adjusting the square wave phase
|
||||
# heartbeat2.opacity = square(0, 180, 150ms, 15) # Weaker pulse
|
||||
# heartbeat2.opacity = square(min_value=0, max_value=180, duration=150ms, duty_cycle=15) # Weaker pulse
|
||||
# heartbeat2.priority = 8
|
||||
#
|
||||
# # Add subtle glow effect
|
||||
# animation heart_glow = solid(0x660000) # Dim red glow
|
||||
# heart_glow.opacity = smooth(30, 100, 1s) # Gentle breathing glow
|
||||
# animation heart_glow = solid(color=0x660000) # Dim red glow
|
||||
# heart_glow.opacity = smooth(min_value=30, max_value=100, duration=1s) # Gentle breathing glow
|
||||
# heart_glow.priority = 5
|
||||
#
|
||||
# # Add center pulse for emphasis
|
||||
# animation center_pulse = pulse_position_animation(
|
||||
# 0xFFFFFF, # White center
|
||||
# 30, # center of strip
|
||||
# 4, # small center
|
||||
# 2 # soft edges
|
||||
# animation center_pulse = beacon_animation(
|
||||
# color=0xFFFFFF, # White center
|
||||
# pos=30, # center of strip
|
||||
# beacon_size=4, # small center
|
||||
# slew_size=2 # soft edges
|
||||
# )
|
||||
# center_pulse.priority = 20
|
||||
# center_pulse.opacity = square(0, 200, 100ms, 10) # Quick white flash
|
||||
# center_pulse.opacity = square(min_value=0, max_value=200, duration=100ms, duty_cycle=10) # Quick white flash
|
||||
#
|
||||
# # Start all animations
|
||||
# run background
|
||||
@ -53,28 +53,61 @@ import animation
|
||||
|
||||
# Heartbeat Pulse - Rhythmic double pulse
|
||||
# Red pulsing like a heartbeat
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var heart_bg_ = 0xFF110000
|
||||
var background_ = animation.solid(animation.global('heart_bg_', 'heart_bg'))
|
||||
# Define heartbeat timing - double pulse pattern
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('heart_bg_', 'heart_bg')
|
||||
# Define heartbeat timing - double pulse animation
|
||||
# First pulse (stronger)
|
||||
var heartbeat1_ = animation.solid(0xFFFF0000) # Bright red
|
||||
animation.global('heartbeat1_').opacity = animation.square(0, 255, 150, 20) # Quick strong pulse
|
||||
var heartbeat1_ = animation.solid(engine)
|
||||
heartbeat1_.color = 0xFFFF0000
|
||||
# Bright red
|
||||
var temp_square_46 = animation.square(engine)
|
||||
temp_square_46.min_value = 0
|
||||
temp_square_46.max_value = 255
|
||||
temp_square_46.duration = 150
|
||||
temp_square_46.duty_cycle = 20
|
||||
animation.global('heartbeat1_').opacity = temp_square_46 # Quick strong pulse
|
||||
animation.global('heartbeat1_').priority = 10
|
||||
# Second pulse (weaker, slightly delayed)
|
||||
var heartbeat2_ = animation.solid(0xFFCC0000) # Slightly dimmer red
|
||||
var heartbeat2_ = animation.solid(engine)
|
||||
heartbeat2_.color = 0xFFCC0000
|
||||
# Slightly dimmer red
|
||||
# Delay the second pulse by adjusting the square wave phase
|
||||
animation.global('heartbeat2_').opacity = animation.square(0, 180, 150, 15) # Weaker pulse
|
||||
var temp_square_92 = animation.square(engine)
|
||||
temp_square_92.min_value = 0
|
||||
temp_square_92.max_value = 180
|
||||
temp_square_92.duration = 150
|
||||
temp_square_92.duty_cycle = 15
|
||||
animation.global('heartbeat2_').opacity = temp_square_92 # Weaker pulse
|
||||
animation.global('heartbeat2_').priority = 8
|
||||
# Add subtle glow effect
|
||||
var heart_glow_ = animation.solid(0xFF660000) # Dim red glow
|
||||
animation.global('heart_glow_').opacity = animation.smooth(30, 100, 1000) # Gentle breathing glow
|
||||
var heart_glow_ = animation.solid(engine)
|
||||
heart_glow_.color = 0xFF660000
|
||||
# Dim red glow
|
||||
var temp_smooth_136 = animation.smooth(engine)
|
||||
temp_smooth_136.min_value = 30
|
||||
temp_smooth_136.max_value = 100
|
||||
temp_smooth_136.duration = 1000
|
||||
animation.global('heart_glow_').opacity = temp_smooth_136 # Gentle breathing glow
|
||||
animation.global('heart_glow_').priority = 5
|
||||
# Add center pulse for emphasis
|
||||
var center_pulse_ = animation.pulse_position_animation(0xFFFFFFFF, 30, 4, 2)
|
||||
var center_pulse_ = animation.beacon_animation(engine)
|
||||
center_pulse_.color = 0xFFFFFFFF
|
||||
center_pulse_.pos = 30
|
||||
center_pulse_.beacon_size = 4
|
||||
center_pulse_.slew_size = 2 # soft edges
|
||||
animation.global('center_pulse_').priority = 20
|
||||
animation.global('center_pulse_').opacity = animation.square(0, 200, 100, 10) # Quick white flash
|
||||
var temp_square_202 = animation.square(engine)
|
||||
temp_square_202.min_value = 0
|
||||
temp_square_202.max_value = 200
|
||||
temp_square_202.duration = 100
|
||||
temp_square_202.duty_cycle = 10
|
||||
animation.global('center_pulse_').opacity = temp_square_202 # Quick white flash
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_background')
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: lava_lamp.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Lava Lamp - Slow flowing warm colors
|
||||
# # Organic movement like a lava lamp
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define lava colors (warm oranges and reds)
|
||||
# palette lava_colors = [
|
||||
@ -21,45 +21,45 @@
|
||||
# ]
|
||||
#
|
||||
# # Base lava animation - very slow color changes
|
||||
# animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 180)
|
||||
# animation lava_base = rich_palette_animation(palette=lava_colors, cycle_period=15s, transition_type=SINE, brightness=180)
|
||||
#
|
||||
# # Add slow-moving lava blobs
|
||||
# pattern blob1_pattern = rich_palette_color_provider(lava_colors, 12s, smooth, 255)
|
||||
# animation lava_blob1 = pulse_position_animation(
|
||||
# blob1_pattern, # color source
|
||||
# 9, # initial position
|
||||
# 18, # large blob
|
||||
# 12 # very soft edges
|
||||
# color blob1_pattern = rich_palette(palette=lava_colors, cycle_period=12s, transition_type=SINE, brightness=255)
|
||||
# animation lava_blob1 = beacon_animation(
|
||||
# color=blob1_pattern, # color source
|
||||
# pos=9, # initial position
|
||||
# beacon_size=18, # large blob
|
||||
# slew_size=12 # very soft edges
|
||||
# )
|
||||
# lava_blob1.priority = 10
|
||||
# lava_blob1.pos = smooth(9, 51, 20s) # Very slow movement
|
||||
# lava_blob1.pos = smooth(min_value=9, max_value=51, duration=20s) # Very slow movement
|
||||
#
|
||||
# pattern blob2_pattern = rich_palette_color_provider(lava_colors, 10s, smooth, 220)
|
||||
# animation lava_blob2 = pulse_position_animation(
|
||||
# blob2_pattern, # color source
|
||||
# 46, # initial position
|
||||
# 14, # medium blob
|
||||
# 10 # soft edges
|
||||
# color blob2_pattern = rich_palette(palette=lava_colors, cycle_period=10s, transition_type=SINE, brightness=220)
|
||||
# animation lava_blob2 = beacon_animation(
|
||||
# color=blob2_pattern, # color source
|
||||
# pos=46, # initial position
|
||||
# beacon_size=14, # medium blob
|
||||
# slew_size=10 # soft edges
|
||||
# )
|
||||
# lava_blob2.priority = 8
|
||||
# lava_blob2.pos = smooth(46, 14, 25s) # Opposite direction, slower
|
||||
# lava_blob2.pos = smooth(min_value=46, max_value=14, duration=25s) # Opposite direction, slower
|
||||
#
|
||||
# pattern blob3_pattern = rich_palette_color_provider(lava_colors, 8s, smooth, 200)
|
||||
# animation lava_blob3 = pulse_position_animation(
|
||||
# blob3_pattern, # color source
|
||||
# 25, # initial position
|
||||
# 10, # smaller blob
|
||||
# 8 # soft edges
|
||||
# color blob3_pattern = rich_palette(palette=lava_colors, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
# animation lava_blob3 = beacon_animation(
|
||||
# color=blob3_pattern, # color source
|
||||
# pos=25, # initial position
|
||||
# beacon_size=10, # smaller blob
|
||||
# slew_size=8 # soft edges
|
||||
# )
|
||||
# lava_blob3.priority = 6
|
||||
# lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
|
||||
# lava_blob3.pos = smooth(min_value=25, max_value=35, duration=18s) # Small movement range
|
||||
#
|
||||
# # Add subtle heat shimmer effect
|
||||
# pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
|
||||
# color shimmer_pattern = rich_palette(palette=lava_colors, cycle_period=6s, transition_type=SINE, brightness=255)
|
||||
# animation heat_shimmer = twinkle_animation(
|
||||
# shimmer_pattern, # color source
|
||||
# 6, # density (shimmer points)
|
||||
# 1.5s # twinkle speed (slow shimmer)
|
||||
# color=shimmer_pattern, # color source
|
||||
# density=6, # density (shimmer points)
|
||||
# twinkle_speed=1.5s # twinkle speed (slow shimmer)
|
||||
# )
|
||||
# heat_shimmer.priority = 15
|
||||
#
|
||||
@ -74,27 +74,77 @@ import animation
|
||||
|
||||
# Lava Lamp - Slow flowing warm colors
|
||||
# Organic movement like a lava lamp
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define lava colors (warm oranges and reds)
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var lava_colors_ = bytes("00330000" "40660000" "80CC3300" "C0FF6600" "FFFFAA00")
|
||||
# Base lava animation - very slow color changes
|
||||
var lava_base_ = animation.rich_palette_animation(animation.global('lava_colors_', 'lava_colors'), 15000, animation.global('smooth_', 'smooth'), 180)
|
||||
var lava_base_ = animation.rich_palette_animation(engine)
|
||||
lava_base_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
lava_base_.cycle_period = 15000
|
||||
lava_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
lava_base_.brightness = 180
|
||||
# Add slow-moving lava blobs
|
||||
var blob1_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 12000, animation.global('smooth_', 'smooth'), 255)
|
||||
var lava_blob1_ = animation.pulse_position_animation(animation.global('blob1_pattern_', 'blob1_pattern'), 9, 18, 12)
|
||||
var blob1_pattern_ = animation.rich_palette(engine)
|
||||
blob1_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob1_pattern_.cycle_period = 12000
|
||||
blob1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob1_pattern_.brightness = 255
|
||||
var lava_blob1_ = animation.beacon_animation(engine)
|
||||
lava_blob1_.color = animation.global('blob1_pattern_', 'blob1_pattern')
|
||||
lava_blob1_.pos = 9
|
||||
lava_blob1_.beacon_size = 18
|
||||
lava_blob1_.slew_size = 12 # very soft edges
|
||||
animation.global('lava_blob1_').priority = 10
|
||||
animation.global('lava_blob1_').pos = animation.smooth(9, 51, 20000) # Very slow movement
|
||||
var blob2_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 10000, animation.global('smooth_', 'smooth'), 220)
|
||||
var lava_blob2_ = animation.pulse_position_animation(animation.global('blob2_pattern_', 'blob2_pattern'), 46, 14, 10)
|
||||
var temp_smooth_148 = animation.smooth(engine)
|
||||
temp_smooth_148.min_value = 9
|
||||
temp_smooth_148.max_value = 51
|
||||
temp_smooth_148.duration = 20000
|
||||
animation.global('lava_blob1_').pos = temp_smooth_148 # Very slow movement
|
||||
var blob2_pattern_ = animation.rich_palette(engine)
|
||||
blob2_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob2_pattern_.cycle_period = 10000
|
||||
blob2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob2_pattern_.brightness = 220
|
||||
var lava_blob2_ = animation.beacon_animation(engine)
|
||||
lava_blob2_.color = animation.global('blob2_pattern_', 'blob2_pattern')
|
||||
lava_blob2_.pos = 46
|
||||
lava_blob2_.beacon_size = 14
|
||||
lava_blob2_.slew_size = 10 # soft edges
|
||||
animation.global('lava_blob2_').priority = 8
|
||||
animation.global('lava_blob2_').pos = animation.smooth(46, 14, 25000) # Opposite direction, slower
|
||||
var blob3_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 8000, animation.global('smooth_', 'smooth'), 200)
|
||||
var lava_blob3_ = animation.pulse_position_animation(animation.global('blob3_pattern_', 'blob3_pattern'), 25, 10, 8)
|
||||
var temp_smooth_228 = animation.smooth(engine)
|
||||
temp_smooth_228.min_value = 46
|
||||
temp_smooth_228.max_value = 14
|
||||
temp_smooth_228.duration = 25000
|
||||
animation.global('lava_blob2_').pos = temp_smooth_228 # Opposite direction, slower
|
||||
var blob3_pattern_ = animation.rich_palette(engine)
|
||||
blob3_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
blob3_pattern_.cycle_period = 8000
|
||||
blob3_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
blob3_pattern_.brightness = 200
|
||||
var lava_blob3_ = animation.beacon_animation(engine)
|
||||
lava_blob3_.color = animation.global('blob3_pattern_', 'blob3_pattern')
|
||||
lava_blob3_.pos = 25
|
||||
lava_blob3_.beacon_size = 10
|
||||
lava_blob3_.slew_size = 8 # soft edges
|
||||
animation.global('lava_blob3_').priority = 6
|
||||
animation.global('lava_blob3_').pos = animation.smooth(25, 35, 18000) # Small movement range
|
||||
var temp_smooth_308 = animation.smooth(engine)
|
||||
temp_smooth_308.min_value = 25
|
||||
temp_smooth_308.max_value = 35
|
||||
temp_smooth_308.duration = 18000
|
||||
animation.global('lava_blob3_').pos = temp_smooth_308 # Small movement range
|
||||
# Add subtle heat shimmer effect
|
||||
var shimmer_pattern_ = animation.rich_palette_color_provider(animation.global('lava_colors_', 'lava_colors'), 6000, animation.global('smooth_', 'smooth'), 255)
|
||||
var heat_shimmer_ = animation.twinkle_animation(animation.global('shimmer_pattern_', 'shimmer_pattern'), 6, 1500)
|
||||
var shimmer_pattern_ = animation.rich_palette(engine)
|
||||
shimmer_pattern_.palette = animation.global('lava_colors_', 'lava_colors')
|
||||
shimmer_pattern_.cycle_period = 6000
|
||||
shimmer_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
shimmer_pattern_.brightness = 255
|
||||
var heat_shimmer_ = animation.twinkle_animation(engine)
|
||||
heat_shimmer_.color = animation.global('shimmer_pattern_', 'shimmer_pattern')
|
||||
heat_shimmer_.density = 6
|
||||
heat_shimmer_.twinkle_speed = 1500 # twinkle speed (slow shimmer)
|
||||
animation.global('heat_shimmer_').priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: lightning_storm.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Lightning Storm - Random lightning flashes
|
||||
# # Dark stormy background with bright lightning
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark stormy background with subtle purple/blue
|
||||
# palette storm_colors = [
|
||||
@ -18,33 +18,33 @@
|
||||
# (255, 0x220033) # Slightly lighter purple
|
||||
# ]
|
||||
#
|
||||
# animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
|
||||
# animation storm_bg = rich_palette_animation(palette=storm_colors, cycle_period=12s, transition_type=SINE, brightness=100)
|
||||
#
|
||||
# # Random lightning flashes - full strip
|
||||
# animation lightning_main = solid(0xFFFFFF) # Bright white
|
||||
# lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
|
||||
# animation lightning_main = solid(color=0xFFFFFF) # Bright white
|
||||
# lightning_main.opacity = square(min_value=0, max_value=255, duration=80ms, duty_cycle=3) # Quick bright flashes
|
||||
# lightning_main.priority = 20
|
||||
#
|
||||
# # Secondary lightning - partial strip
|
||||
# animation lightning_partial = pulse_position_animation(
|
||||
# 0xFFFFAA, # Slightly yellow white
|
||||
# 30, # center position
|
||||
# 20, # covers part of strip
|
||||
# 5 # soft edges
|
||||
# animation lightning_partial = beacon_animation(
|
||||
# color=0xFFFFAA, # Slightly yellow white
|
||||
# pos=30, # center position
|
||||
# beacon_size=20, # covers part of strip
|
||||
# slew_size=5 # soft edges
|
||||
# )
|
||||
# lightning_partial.priority = 15
|
||||
# lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
|
||||
# lightning_partial.opacity = square(min_value=0, max_value=200, duration=120ms, duty_cycle=4) # Different timing
|
||||
#
|
||||
# # Add blue afterglow
|
||||
# animation afterglow = solid(0x4444FF) # Blue glow
|
||||
# afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
|
||||
# animation afterglow = solid(color=0x4444FF) # Blue glow
|
||||
# afterglow.opacity = square(min_value=0, max_value=80, duration=200ms, duty_cycle=8) # Longer, dimmer glow
|
||||
# afterglow.priority = 10
|
||||
#
|
||||
# # Distant thunder (dim flashes)
|
||||
# animation distant_flash = twinkle_animation(
|
||||
# 0x666699, # Dim blue-white
|
||||
# 4, # density (few flashes)
|
||||
# 300ms # twinkle speed (medium duration)
|
||||
# color=0x666699, # Dim blue-white
|
||||
# density=4, # density (few flashes)
|
||||
# twinkle_speed=300ms # twinkle speed (medium duration)
|
||||
# )
|
||||
# distant_flash.priority = 5
|
||||
#
|
||||
@ -59,24 +59,57 @@ import animation
|
||||
|
||||
# Lightning Storm - Random lightning flashes
|
||||
# Dark stormy background with bright lightning
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark stormy background with subtle purple/blue
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var storm_colors_ = bytes("00000011" "80110022" "FF220033")
|
||||
var storm_bg_ = animation.rich_palette_animation(animation.global('storm_colors_', 'storm_colors'), 12000, animation.global('smooth_', 'smooth'), 100)
|
||||
var storm_bg_ = animation.rich_palette_animation(engine)
|
||||
storm_bg_.palette = animation.global('storm_colors_', 'storm_colors')
|
||||
storm_bg_.cycle_period = 12000
|
||||
storm_bg_.transition_type = animation.global('SINE_', 'SINE')
|
||||
storm_bg_.brightness = 100
|
||||
# Random lightning flashes - full strip
|
||||
var lightning_main_ = animation.solid(0xFFFFFFFF) # Bright white
|
||||
animation.global('lightning_main_').opacity = animation.square(0, 255, 80, 3) # Quick bright flashes
|
||||
var lightning_main_ = animation.solid(engine)
|
||||
lightning_main_.color = 0xFFFFFFFF
|
||||
# Bright white
|
||||
var temp_square_82 = animation.square(engine)
|
||||
temp_square_82.min_value = 0
|
||||
temp_square_82.max_value = 255
|
||||
temp_square_82.duration = 80
|
||||
temp_square_82.duty_cycle = 3
|
||||
animation.global('lightning_main_').opacity = temp_square_82 # Quick bright flashes
|
||||
animation.global('lightning_main_').priority = 20
|
||||
# Secondary lightning - partial strip
|
||||
var lightning_partial_ = animation.pulse_position_animation(0xFFFFFFAA, 30, 20, 5)
|
||||
var lightning_partial_ = animation.beacon_animation(engine)
|
||||
lightning_partial_.color = 0xFFFFFFAA
|
||||
lightning_partial_.pos = 30
|
||||
lightning_partial_.beacon_size = 20
|
||||
lightning_partial_.slew_size = 5 # soft edges
|
||||
animation.global('lightning_partial_').priority = 15
|
||||
animation.global('lightning_partial_').opacity = animation.square(0, 200, 120, 4) # Different timing
|
||||
var temp_square_152 = animation.square(engine)
|
||||
temp_square_152.min_value = 0
|
||||
temp_square_152.max_value = 200
|
||||
temp_square_152.duration = 120
|
||||
temp_square_152.duty_cycle = 4
|
||||
animation.global('lightning_partial_').opacity = temp_square_152 # Different timing
|
||||
# Add blue afterglow
|
||||
var afterglow_ = animation.solid(0xFF4444FF) # Blue glow
|
||||
animation.global('afterglow_').opacity = animation.square(0, 80, 200, 8) # Longer, dimmer glow
|
||||
var afterglow_ = animation.solid(engine)
|
||||
afterglow_.color = 0xFF4444FF
|
||||
# Blue glow
|
||||
var temp_square_190 = animation.square(engine)
|
||||
temp_square_190.min_value = 0
|
||||
temp_square_190.max_value = 80
|
||||
temp_square_190.duration = 200
|
||||
temp_square_190.duty_cycle = 8
|
||||
animation.global('afterglow_').opacity = temp_square_190 # Longer, dimmer glow
|
||||
animation.global('afterglow_').priority = 10
|
||||
# Distant thunder (dim flashes)
|
||||
var distant_flash_ = animation.twinkle_animation(0xFF666699, 4, 300)
|
||||
var distant_flash_ = animation.twinkle_animation(engine)
|
||||
distant_flash_.color = 0xFF666699
|
||||
distant_flash_.density = 4
|
||||
distant_flash_.twinkle_speed = 300 # twinkle speed (medium duration)
|
||||
animation.global('distant_flash_').priority = 5
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,18 +2,18 @@
|
||||
# Source: matrix_rain.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Matrix Rain - Digital rain effect
|
||||
# # Green cascading code like The Matrix
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark background
|
||||
# color matrix_bg = 0x000000
|
||||
# animation background = solid(matrix_bg)
|
||||
# animation background = solid(color=matrix_bg)
|
||||
#
|
||||
# # Define matrix green palette
|
||||
# palette matrix_greens = [
|
||||
@ -25,35 +25,35 @@
|
||||
# ]
|
||||
#
|
||||
# # Create multiple cascading streams
|
||||
# pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
|
||||
# color stream1_pattern = rich_palette(palette=matrix_greens, cycle_period=2s, transition_type=LINEAR, brightness=255)
|
||||
# animation stream1 = comet_animation(
|
||||
# stream1_pattern, # color source
|
||||
# 15, # long tail
|
||||
# 1.5s # speed
|
||||
# color=stream1_pattern, # color source
|
||||
# tail_length=15, # long tail
|
||||
# speed=1.5s # speed
|
||||
# )
|
||||
# stream1.priority = 10
|
||||
#
|
||||
# pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
|
||||
# color stream2_pattern = rich_palette(palette=matrix_greens, cycle_period=1.8s, transition_type=LINEAR, brightness=200)
|
||||
# animation stream2 = comet_animation(
|
||||
# stream2_pattern, # color source
|
||||
# 12, # medium tail
|
||||
# 2.2s # different speed
|
||||
# color=stream2_pattern, # color source
|
||||
# tail_length=12, # medium tail
|
||||
# speed=2.2s # different speed
|
||||
# )
|
||||
# stream2.priority = 8
|
||||
#
|
||||
# pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
|
||||
# color stream3_pattern = rich_palette(palette=matrix_greens, cycle_period=2.5s, transition_type=LINEAR, brightness=180)
|
||||
# animation stream3 = comet_animation(
|
||||
# stream3_pattern, # color source
|
||||
# 10, # shorter tail
|
||||
# 1.8s # another speed
|
||||
# color=stream3_pattern, # color source
|
||||
# tail_length=10, # shorter tail
|
||||
# speed=1.8s # another speed
|
||||
# )
|
||||
# stream3.priority = 6
|
||||
#
|
||||
# # Add random bright flashes (like code highlights)
|
||||
# animation code_flash = twinkle_animation(
|
||||
# 0x00FFAA, # Bright cyan-green
|
||||
# 3, # density (few flashes)
|
||||
# 150ms # twinkle speed (quick flash)
|
||||
# color=0x00FFAA, # Bright cyan-green
|
||||
# density=3, # density (few flashes)
|
||||
# twinkle_speed=150ms # twinkle speed (quick flash)
|
||||
# )
|
||||
# code_flash.priority = 20
|
||||
#
|
||||
@ -68,24 +68,52 @@ import animation
|
||||
|
||||
# Matrix Rain - Digital rain effect
|
||||
# Green cascading code like The Matrix
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var matrix_bg_ = 0xFF000000
|
||||
var background_ = animation.solid(animation.global('matrix_bg_', 'matrix_bg'))
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('matrix_bg_', 'matrix_bg')
|
||||
# Define matrix green palette
|
||||
var matrix_greens_ = bytes("00000000" "40003300" "80006600" "C000AA00" "FF00FF00")
|
||||
# Create multiple cascading streams
|
||||
var stream1_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 2000, animation.global('linear_', 'linear'), 255)
|
||||
var stream1_ = animation.comet_animation(animation.global('stream1_pattern_', 'stream1_pattern'), 15, 1500)
|
||||
var stream1_pattern_ = animation.rich_palette(engine)
|
||||
stream1_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream1_pattern_.cycle_period = 2000
|
||||
stream1_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream1_pattern_.brightness = 255
|
||||
var stream1_ = animation.comet_animation(engine)
|
||||
stream1_.color = animation.global('stream1_pattern_', 'stream1_pattern')
|
||||
stream1_.tail_length = 15
|
||||
stream1_.speed = 1500 # speed
|
||||
animation.global('stream1_').priority = 10
|
||||
var stream2_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 1800, animation.global('linear_', 'linear'), 200)
|
||||
var stream2_ = animation.comet_animation(animation.global('stream2_pattern_', 'stream2_pattern'), 12, 2200)
|
||||
var stream2_pattern_ = animation.rich_palette(engine)
|
||||
stream2_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream2_pattern_.cycle_period = 1800
|
||||
stream2_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream2_pattern_.brightness = 200
|
||||
var stream2_ = animation.comet_animation(engine)
|
||||
stream2_.color = animation.global('stream2_pattern_', 'stream2_pattern')
|
||||
stream2_.tail_length = 12
|
||||
stream2_.speed = 2200 # different speed
|
||||
animation.global('stream2_').priority = 8
|
||||
var stream3_pattern_ = animation.rich_palette_color_provider(animation.global('matrix_greens_', 'matrix_greens'), 2500, animation.global('linear_', 'linear'), 180)
|
||||
var stream3_ = animation.comet_animation(animation.global('stream3_pattern_', 'stream3_pattern'), 10, 1800)
|
||||
var stream3_pattern_ = animation.rich_palette(engine)
|
||||
stream3_pattern_.palette = animation.global('matrix_greens_', 'matrix_greens')
|
||||
stream3_pattern_.cycle_period = 2500
|
||||
stream3_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
stream3_pattern_.brightness = 180
|
||||
var stream3_ = animation.comet_animation(engine)
|
||||
stream3_.color = animation.global('stream3_pattern_', 'stream3_pattern')
|
||||
stream3_.tail_length = 10
|
||||
stream3_.speed = 1800 # another speed
|
||||
animation.global('stream3_').priority = 6
|
||||
# Add random bright flashes (like code highlights)
|
||||
var code_flash_ = animation.twinkle_animation(0xFF00FFAA, 3, 150)
|
||||
var code_flash_ = animation.twinkle_animation(engine)
|
||||
code_flash_.color = 0xFF00FFAA
|
||||
code_flash_.density = 3
|
||||
code_flash_.twinkle_speed = 150 # twinkle speed (quick flash)
|
||||
animation.global('code_flash_').priority = 20
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,61 +2,61 @@
|
||||
# Source: meteor_shower.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Meteor Shower - Multiple meteors with trails
|
||||
# # Fast moving bright objects with fading trails
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark space background
|
||||
# color space_bg = 0x000011
|
||||
# animation background = solid(space_bg)
|
||||
# animation background = solid(color=space_bg)
|
||||
#
|
||||
# # Multiple meteors with different speeds and colors
|
||||
# animation meteor1 = comet_animation(
|
||||
# 0xFFFFFF, # Bright white
|
||||
# 12, # long trail
|
||||
# 1.5s # fast speed
|
||||
# color=0xFFFFFF, # Bright white
|
||||
# tail_length=12, # long trail
|
||||
# speed=1.5s # fast speed
|
||||
# )
|
||||
# meteor1.priority = 15
|
||||
#
|
||||
# animation meteor2 = comet_animation(
|
||||
# 0xFFAA00, # Orange
|
||||
# 10, # medium trail
|
||||
# 2s # medium speed
|
||||
# color=0xFFAA00, # Orange
|
||||
# tail_length=10, # medium trail
|
||||
# speed=2s # medium speed
|
||||
# )
|
||||
# meteor2.priority = 12
|
||||
#
|
||||
# animation meteor3 = comet_animation(
|
||||
# 0xAAAAFF, # Blue-white
|
||||
# 8, # shorter trail
|
||||
# 1.8s # fast speed
|
||||
# color=0xAAAAFF, # Blue-white
|
||||
# tail_length=8, # shorter trail
|
||||
# speed=1.8s # fast speed
|
||||
# )
|
||||
# meteor3.priority = 10
|
||||
#
|
||||
# animation meteor4 = comet_animation(
|
||||
# 0xFFAAAA, # Pink-white
|
||||
# 14, # long trail
|
||||
# 2.5s # slower speed
|
||||
# color=0xFFAAAA, # Pink-white
|
||||
# tail_length=14, # long trail
|
||||
# speed=2.5s # slower speed
|
||||
# )
|
||||
# meteor4.priority = 8
|
||||
#
|
||||
# # Add distant stars
|
||||
# animation stars = twinkle_animation(
|
||||
# 0xCCCCCC, # Dim white
|
||||
# 12, # density (many stars)
|
||||
# 2s # twinkle speed (slow twinkle)
|
||||
# color=0xCCCCCC, # Dim white
|
||||
# density=12, # density (many stars)
|
||||
# twinkle_speed=2s # twinkle speed (slow twinkle)
|
||||
# )
|
||||
# stars.priority = 5
|
||||
#
|
||||
# # Add occasional bright flash (meteor explosion)
|
||||
# animation meteor_flash = twinkle_animation(
|
||||
# 0xFFFFFF, # Bright white
|
||||
# 1, # density (single flash)
|
||||
# 100ms # twinkle speed (very quick)
|
||||
# color=0xFFFFFF, # Bright white
|
||||
# density=1, # density (single flash)
|
||||
# twinkle_speed=100ms # twinkle speed (very quick)
|
||||
# )
|
||||
# meteor_flash.priority = 25
|
||||
#
|
||||
@ -73,24 +73,46 @@ import animation
|
||||
|
||||
# Meteor Shower - Multiple meteors with trails
|
||||
# Fast moving bright objects with fading trails
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark space background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var space_bg_ = 0xFF000011
|
||||
var background_ = animation.solid(animation.global('space_bg_', 'space_bg'))
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('space_bg_', 'space_bg')
|
||||
# Multiple meteors with different speeds and colors
|
||||
var meteor1_ = animation.comet_animation(0xFFFFFFFF, 12, 1500)
|
||||
var meteor1_ = animation.comet_animation(engine)
|
||||
meteor1_.color = 0xFFFFFFFF
|
||||
meteor1_.tail_length = 12
|
||||
meteor1_.speed = 1500 # fast speed
|
||||
animation.global('meteor1_').priority = 15
|
||||
var meteor2_ = animation.comet_animation(0xFFFFAA00, 10, 2000)
|
||||
var meteor2_ = animation.comet_animation(engine)
|
||||
meteor2_.color = 0xFFFFAA00
|
||||
meteor2_.tail_length = 10
|
||||
meteor2_.speed = 2000 # medium speed
|
||||
animation.global('meteor2_').priority = 12
|
||||
var meteor3_ = animation.comet_animation(0xFFAAAAFF, 8, 1800)
|
||||
var meteor3_ = animation.comet_animation(engine)
|
||||
meteor3_.color = 0xFFAAAAFF
|
||||
meteor3_.tail_length = 8
|
||||
meteor3_.speed = 1800 # fast speed
|
||||
animation.global('meteor3_').priority = 10
|
||||
var meteor4_ = animation.comet_animation(0xFFFFAAAA, 14, 2500)
|
||||
var meteor4_ = animation.comet_animation(engine)
|
||||
meteor4_.color = 0xFFFFAAAA
|
||||
meteor4_.tail_length = 14
|
||||
meteor4_.speed = 2500 # slower speed
|
||||
animation.global('meteor4_').priority = 8
|
||||
# Add distant stars
|
||||
var stars_ = animation.twinkle_animation(0xFFCCCCCC, 12, 2000)
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFCCCCCC
|
||||
stars_.density = 12
|
||||
stars_.twinkle_speed = 2000 # twinkle speed (slow twinkle)
|
||||
animation.global('stars_').priority = 5
|
||||
# Add occasional bright flash (meteor explosion)
|
||||
var meteor_flash_ = animation.twinkle_animation(0xFFFFFFFF, 1, 100)
|
||||
var meteor_flash_ = animation.twinkle_animation(engine)
|
||||
meteor_flash_.color = 0xFFFFFFFF
|
||||
meteor_flash_.density = 1
|
||||
meteor_flash_.twinkle_speed = 100 # twinkle speed (very quick)
|
||||
animation.global('meteor_flash_').priority = 25
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: neon_glow.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Neon Glow - Electric neon tube effect
|
||||
# # Bright saturated colors with flickering
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define neon colors
|
||||
# palette neon_colors = [
|
||||
@ -20,47 +20,47 @@
|
||||
# ]
|
||||
#
|
||||
# # Main neon glow with color cycling
|
||||
# animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
|
||||
# animation neon_main = rich_palette_animation(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
|
||||
#
|
||||
# # Add electrical flickering
|
||||
# neon_main.opacity = smooth(220, 255, 200ms)
|
||||
# neon_main.opacity = smooth(min_value=220, max_value=255, duration=200ms)
|
||||
#
|
||||
# # Add occasional electrical surge
|
||||
# animation neon_surge = solid(0xFFFFFF) # White surge
|
||||
# neon_surge.opacity = square(0, 255, 50ms, 2) # Quick bright surges
|
||||
# animation neon_surge = solid(color=0xFFFFFF) # White surge
|
||||
# neon_surge.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=2) # Quick bright surges
|
||||
# neon_surge.priority = 20
|
||||
#
|
||||
# # Add neon tube segments with gaps
|
||||
# pattern segment_pattern = rich_palette_color_provider(neon_colors, 4s, linear, 255)
|
||||
# animation segment1 = pulse_position_animation(
|
||||
# segment_pattern, # color source
|
||||
# 6, # position
|
||||
# 12, # segment length
|
||||
# 1 # sharp edges
|
||||
# color segment_pattern = rich_palette(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
|
||||
# animation segment1 = beacon_animation(
|
||||
# color=segment_pattern, # color source
|
||||
# pos=6, # position
|
||||
# beacon_size=12, # segment length
|
||||
# slew_size=1 # sharp edges
|
||||
# )
|
||||
# segment1.priority = 10
|
||||
#
|
||||
# animation segment2 = pulse_position_animation(
|
||||
# segment_pattern, # color source
|
||||
# 24, # position
|
||||
# 12, # segment length
|
||||
# 1 # sharp edges
|
||||
# animation segment2 = beacon_animation(
|
||||
# color=segment_pattern, # color source
|
||||
# pos=24, # position
|
||||
# beacon_size=12, # segment length
|
||||
# slew_size=1 # sharp edges
|
||||
# )
|
||||
# segment2.priority = 10
|
||||
#
|
||||
# animation segment3 = pulse_position_animation(
|
||||
# segment_pattern, # color source
|
||||
# 42, # position
|
||||
# 12, # segment length
|
||||
# 1 # sharp edges
|
||||
# animation segment3 = beacon_animation(
|
||||
# color=segment_pattern, # color source
|
||||
# pos=42, # position
|
||||
# beacon_size=12, # segment length
|
||||
# slew_size=1 # sharp edges
|
||||
# )
|
||||
# segment3.priority = 10
|
||||
#
|
||||
# # Add electrical arcing between segments
|
||||
# animation arc_sparkles = twinkle_animation(
|
||||
# 0xAAAAFF, # Electric blue
|
||||
# 4, # density (few arcs)
|
||||
# 100ms # twinkle speed (quick arcs)
|
||||
# color=0xAAAAFF, # Electric blue
|
||||
# density=4, # density (few arcs)
|
||||
# twinkle_speed=100ms # twinkle speed (quick arcs)
|
||||
# )
|
||||
# arc_sparkles.priority = 15
|
||||
#
|
||||
@ -76,27 +76,64 @@ import animation
|
||||
|
||||
# Neon Glow - Electric neon tube effect
|
||||
# Bright saturated colors with flickering
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define neon colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var neon_colors_ = bytes("00FF0080" "5500FF80" "AA8000FF" "FFFF8000")
|
||||
# Main neon glow with color cycling
|
||||
var neon_main_ = animation.rich_palette_animation(animation.global('neon_colors_', 'neon_colors'), 4000, animation.global('linear_', 'linear'), 255)
|
||||
var neon_main_ = animation.rich_palette_animation(engine)
|
||||
neon_main_.palette = animation.global('neon_colors_', 'neon_colors')
|
||||
neon_main_.cycle_period = 4000
|
||||
neon_main_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
neon_main_.brightness = 255
|
||||
# Add electrical flickering
|
||||
animation.global('neon_main_').opacity = animation.smooth(220, 255, 200)
|
||||
var temp_smooth_81 = animation.smooth(engine)
|
||||
temp_smooth_81.min_value = 220
|
||||
temp_smooth_81.max_value = 255
|
||||
temp_smooth_81.duration = 200
|
||||
animation.global('neon_main_').opacity = temp_smooth_81
|
||||
# Add occasional electrical surge
|
||||
var neon_surge_ = animation.solid(0xFFFFFFFF) # White surge
|
||||
animation.global('neon_surge_').opacity = animation.square(0, 255, 50, 2) # Quick bright surges
|
||||
var neon_surge_ = animation.solid(engine)
|
||||
neon_surge_.color = 0xFFFFFFFF
|
||||
# White surge
|
||||
var temp_square_114 = animation.square(engine)
|
||||
temp_square_114.min_value = 0
|
||||
temp_square_114.max_value = 255
|
||||
temp_square_114.duration = 50
|
||||
temp_square_114.duty_cycle = 2
|
||||
animation.global('neon_surge_').opacity = temp_square_114 # Quick bright surges
|
||||
animation.global('neon_surge_').priority = 20
|
||||
# Add neon tube segments with gaps
|
||||
var segment_pattern_ = animation.rich_palette_color_provider(animation.global('neon_colors_', 'neon_colors'), 4000, animation.global('linear_', 'linear'), 255)
|
||||
var segment1_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 6, 12, 1)
|
||||
var segment_pattern_ = animation.rich_palette(engine)
|
||||
segment_pattern_.palette = animation.global('neon_colors_', 'neon_colors')
|
||||
segment_pattern_.cycle_period = 4000
|
||||
segment_pattern_.transition_type = animation.global('LINEAR_', 'LINEAR')
|
||||
segment_pattern_.brightness = 255
|
||||
var segment1_ = animation.beacon_animation(engine)
|
||||
segment1_.color = animation.global('segment_pattern_', 'segment_pattern')
|
||||
segment1_.pos = 6
|
||||
segment1_.beacon_size = 12
|
||||
segment1_.slew_size = 1 # sharp edges
|
||||
animation.global('segment1_').priority = 10
|
||||
var segment2_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 24, 12, 1)
|
||||
var segment2_ = animation.beacon_animation(engine)
|
||||
segment2_.color = animation.global('segment_pattern_', 'segment_pattern')
|
||||
segment2_.pos = 24
|
||||
segment2_.beacon_size = 12
|
||||
segment2_.slew_size = 1 # sharp edges
|
||||
animation.global('segment2_').priority = 10
|
||||
var segment3_ = animation.pulse_position_animation(animation.global('segment_pattern_', 'segment_pattern'), 42, 12, 1)
|
||||
var segment3_ = animation.beacon_animation(engine)
|
||||
segment3_.color = animation.global('segment_pattern_', 'segment_pattern')
|
||||
segment3_.pos = 42
|
||||
segment3_.beacon_size = 12
|
||||
segment3_.slew_size = 1 # sharp edges
|
||||
animation.global('segment3_').priority = 10
|
||||
# Add electrical arcing between segments
|
||||
var arc_sparkles_ = animation.twinkle_animation(0xFFAAAAFF, 4, 100)
|
||||
var arc_sparkles_ = animation.twinkle_animation(engine)
|
||||
arc_sparkles_.color = 0xFFAAAAFF
|
||||
arc_sparkles_.density = 4
|
||||
arc_sparkles_.twinkle_speed = 100 # twinkle speed (quick arcs)
|
||||
animation.global('arc_sparkles_').priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: ocean_waves.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Ocean Waves - Blue-green wave simulation
|
||||
# # Flowing water colors with wave motion
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define ocean color palette
|
||||
# palette ocean_colors = [
|
||||
@ -21,34 +21,34 @@
|
||||
# ]
|
||||
#
|
||||
# # Base ocean animation with slow color cycling
|
||||
# animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 200)
|
||||
# animation ocean_base = rich_palette_animation(palette=ocean_colors, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
#
|
||||
# # Add wave motion with moving pulses
|
||||
# pattern wave1_pattern = rich_palette_color_provider(ocean_colors, 6s, smooth, 255)
|
||||
# animation wave1 = pulse_position_animation(
|
||||
# wave1_pattern, # color source
|
||||
# 0, # initial position
|
||||
# 12, # wave width
|
||||
# 6 # soft edges
|
||||
# color wave1_pattern = rich_palette(palette=ocean_colors, cycle_period=6s, transition_type=SINE, brightness=255)
|
||||
# animation wave1 = beacon_animation(
|
||||
# color=wave1_pattern, # color source
|
||||
# pos=0, # initial position
|
||||
# beacon_size=12, # wave width
|
||||
# slew_size=6 # soft edges
|
||||
# )
|
||||
# wave1.priority = 10
|
||||
# wave1.pos = sawtooth(0, 48, 5s) # 60-12 = 48
|
||||
# wave1.pos = sawtooth(min_value=0, max_value=48, duration=5s) # 60-12 = 48
|
||||
#
|
||||
# pattern wave2_pattern = rich_palette_color_provider(ocean_colors, 4s, smooth, 180)
|
||||
# animation wave2 = pulse_position_animation(
|
||||
# wave2_pattern, # color source
|
||||
# 52, # initial position
|
||||
# 8, # smaller wave
|
||||
# 4 # soft edges
|
||||
# color wave2_pattern = rich_palette(palette=ocean_colors, cycle_period=4s, transition_type=SINE, brightness=180)
|
||||
# animation wave2 = beacon_animation(
|
||||
# color=wave2_pattern, # color source
|
||||
# pos=52, # initial position
|
||||
# beacon_size=8, # smaller wave
|
||||
# slew_size=4 # soft edges
|
||||
# )
|
||||
# wave2.priority = 8
|
||||
# wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
|
||||
# wave2.pos = sawtooth(min_value=52, max_value=8, duration=7s) # Opposite direction
|
||||
#
|
||||
# # Add foam sparkles
|
||||
# animation foam = twinkle_animation(
|
||||
# 0xFFFFFF, # White foam
|
||||
# 6, # density (sparkle count)
|
||||
# 300ms # twinkle speed (quick sparkles)
|
||||
# color=0xFFFFFF, # White foam
|
||||
# density=6, # density (sparkle count)
|
||||
# twinkle_speed=300ms # twinkle speed (quick sparkles)
|
||||
# )
|
||||
# foam.priority = 15
|
||||
#
|
||||
@ -62,22 +62,56 @@ import animation
|
||||
|
||||
# Ocean Waves - Blue-green wave simulation
|
||||
# Flowing water colors with wave motion
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define ocean color palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var ocean_colors_ = bytes("00000080" "400040C0" "800080FF" "C040C0FF" "FF80FFFF")
|
||||
# Base ocean animation with slow color cycling
|
||||
var ocean_base_ = animation.rich_palette_animation(animation.global('ocean_colors_', 'ocean_colors'), 8000, animation.global('smooth_', 'smooth'), 200)
|
||||
var ocean_base_ = animation.rich_palette_animation(engine)
|
||||
ocean_base_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
ocean_base_.cycle_period = 8000
|
||||
ocean_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
ocean_base_.brightness = 200
|
||||
# Add wave motion with moving pulses
|
||||
var wave1_pattern_ = animation.rich_palette_color_provider(animation.global('ocean_colors_', 'ocean_colors'), 6000, animation.global('smooth_', 'smooth'), 255)
|
||||
var wave1_ = animation.pulse_position_animation(animation.global('wave1_pattern_', 'wave1_pattern'), 0, 12, 6)
|
||||
var wave1_pattern_ = animation.rich_palette(engine)
|
||||
wave1_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
wave1_pattern_.cycle_period = 6000
|
||||
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave1_pattern_.brightness = 255
|
||||
var wave1_ = animation.beacon_animation(engine)
|
||||
wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern')
|
||||
wave1_.pos = 0
|
||||
wave1_.beacon_size = 12
|
||||
wave1_.slew_size = 6 # soft edges
|
||||
animation.global('wave1_').priority = 10
|
||||
animation.global('wave1_').pos = animation.sawtooth(0, 48, 5000) # 60-12 = 48
|
||||
var wave2_pattern_ = animation.rich_palette_color_provider(animation.global('ocean_colors_', 'ocean_colors'), 4000, animation.global('smooth_', 'smooth'), 180)
|
||||
var wave2_ = animation.pulse_position_animation(animation.global('wave2_pattern_', 'wave2_pattern'), 52, 8, 4)
|
||||
var temp_sawtooth_148 = animation.sawtooth(engine)
|
||||
temp_sawtooth_148.min_value = 0
|
||||
temp_sawtooth_148.max_value = 48
|
||||
temp_sawtooth_148.duration = 5000
|
||||
animation.global('wave1_').pos = temp_sawtooth_148 # 60-12 = 48
|
||||
var wave2_pattern_ = animation.rich_palette(engine)
|
||||
wave2_pattern_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
wave2_pattern_.cycle_period = 4000
|
||||
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave2_pattern_.brightness = 180
|
||||
var wave2_ = animation.beacon_animation(engine)
|
||||
wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern')
|
||||
wave2_.pos = 52
|
||||
wave2_.beacon_size = 8
|
||||
wave2_.slew_size = 4 # soft edges
|
||||
animation.global('wave2_').priority = 8
|
||||
animation.global('wave2_').pos = animation.sawtooth(52, 8, 7000) # Opposite direction
|
||||
var temp_sawtooth_228 = animation.sawtooth(engine)
|
||||
temp_sawtooth_228.min_value = 52
|
||||
temp_sawtooth_228.max_value = 8
|
||||
temp_sawtooth_228.duration = 7000
|
||||
animation.global('wave2_').pos = temp_sawtooth_228 # Opposite direction
|
||||
# Add foam sparkles
|
||||
var foam_ = animation.twinkle_animation(0xFFFFFFFF, 6, 300)
|
||||
var foam_ = animation.twinkle_animation(engine)
|
||||
foam_.color = 0xFFFFFFFF
|
||||
foam_.density = 6
|
||||
foam_.twinkle_speed = 300 # twinkle speed (quick sparkles)
|
||||
animation.global('foam_').priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: palette_demo.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Palette Demo - Shows how to use custom palettes in DSL
|
||||
# # This demonstrates the new palette syntax
|
||||
#
|
||||
# strip length 30
|
||||
# #strip length 30
|
||||
#
|
||||
# # Define a fire palette
|
||||
# palette fire_colors = [
|
||||
@ -30,9 +30,9 @@
|
||||
# ]
|
||||
#
|
||||
# # Create animations using the palettes
|
||||
# animation fire_anim = rich_palette_animation(fire_colors, 5s)
|
||||
# animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5s)
|
||||
#
|
||||
# animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
|
||||
# animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
|
||||
#
|
||||
# # Sequence to show both palettes
|
||||
# sequence palette_demo {
|
||||
@ -51,14 +51,21 @@ import animation
|
||||
|
||||
# Palette Demo - Shows how to use custom palettes in DSL
|
||||
# This demonstrates the new palette syntax
|
||||
var engine = animation.init_strip(30)
|
||||
#strip length 30
|
||||
# Define a fire palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var fire_colors_ = bytes("00000000" "40800000" "80FF0000" "C0FF8000" "FFFFFF00")
|
||||
# Define an ocean palette
|
||||
var ocean_colors_ = bytes("00000080" "400000FF" "8000FFFF" "C000FF80" "FF008000")
|
||||
# Create animations using the palettes
|
||||
var fire_anim_ = animation.rich_palette_animation(animation.global('fire_colors_', 'fire_colors'), 5000)
|
||||
var ocean_anim_ = animation.rich_palette_animation(animation.global('ocean_colors_', 'ocean_colors'), 8000)
|
||||
var fire_anim_ = animation.rich_palette_animation(engine)
|
||||
fire_anim_.palette = animation.global('fire_colors_', 'fire_colors')
|
||||
fire_anim_.cycle_period = 5000
|
||||
var ocean_anim_ = animation.rich_palette_animation(engine)
|
||||
ocean_anim_.palette = animation.global('ocean_colors_', 'ocean_colors')
|
||||
ocean_anim_.cycle_period = 8000
|
||||
# Sequence to show both palettes
|
||||
def sequence_palette_demo()
|
||||
var steps = []
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: palette_showcase.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Palette Showcase - Demonstrates all palette features
|
||||
# # This example shows the full range of palette capabilities
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Example 1: Fire palette with hex colors
|
||||
# palette fire_gradient = [
|
||||
@ -52,13 +52,13 @@
|
||||
# ]
|
||||
#
|
||||
# # Create animations using each palette
|
||||
# animation fire_effect = rich_palette_animation(fire_gradient, 3s)
|
||||
# animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
|
||||
#
|
||||
# animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
|
||||
# animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
#
|
||||
# animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
|
||||
# animation aurora_lights = rich_palette_animation(palette=aurora_borealis, cycle_period=12s, transition_type=SINE, brightness=180)
|
||||
#
|
||||
# animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
|
||||
# animation sunset_glow = rich_palette_animation(palette=sunset_sky, cycle_period=6s, transition_type=SINE, brightness=220)
|
||||
#
|
||||
# # Sequence to showcase all palettes
|
||||
# sequence palette_showcase {
|
||||
@ -92,8 +92,11 @@ import animation
|
||||
|
||||
# Palette Showcase - Demonstrates all palette features
|
||||
# This example shows the full range of palette capabilities
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Example 1: Fire palette with hex colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var fire_gradient_ = bytes("00000000" "20330000" "40660000" "60CC0000" "80FF3300" "A0FF6600" "C0FF9900" "E0FFCC00" "FFFFFF00")
|
||||
# Example 2: Ocean palette with named colors
|
||||
var ocean_depths_ = bytes("00000000" "40000080" "800000FF" "C000FFFF" "FFFFFFFF")
|
||||
@ -102,10 +105,24 @@ var aurora_borealis_ = bytes("00000022" "40004400" "8000AA44" "C044AA88" "FF88FF
|
||||
# Example 4: Sunset palette mixing hex and named colors
|
||||
var sunset_sky_ = bytes("00191970" "40800080" "80FF69B4" "C0FFA500" "FFFFFF00")
|
||||
# Create animations using each palette
|
||||
var fire_effect_ = animation.rich_palette_animation(animation.global('fire_gradient_', 'fire_gradient'), 3000)
|
||||
var ocean_waves_ = animation.rich_palette_animation(animation.global('ocean_depths_', 'ocean_depths'), 8000, animation.global('smooth_', 'smooth'), 200)
|
||||
var aurora_lights_ = animation.rich_palette_animation(animation.global('aurora_borealis_', 'aurora_borealis'), 12000, animation.global('smooth_', 'smooth'), 180)
|
||||
var sunset_glow_ = animation.rich_palette_animation(animation.global('sunset_sky_', 'sunset_sky'), 6000, animation.global('smooth_', 'smooth'), 220)
|
||||
var fire_effect_ = animation.rich_palette_animation(engine)
|
||||
fire_effect_.palette = animation.global('fire_gradient_', 'fire_gradient')
|
||||
fire_effect_.cycle_period = 3000
|
||||
var ocean_waves_ = animation.rich_palette_animation(engine)
|
||||
ocean_waves_.palette = animation.global('ocean_depths_', 'ocean_depths')
|
||||
ocean_waves_.cycle_period = 8000
|
||||
ocean_waves_.transition_type = animation.global('SINE_', 'SINE')
|
||||
ocean_waves_.brightness = 200
|
||||
var aurora_lights_ = animation.rich_palette_animation(engine)
|
||||
aurora_lights_.palette = animation.global('aurora_borealis_', 'aurora_borealis')
|
||||
aurora_lights_.cycle_period = 12000
|
||||
aurora_lights_.transition_type = animation.global('SINE_', 'SINE')
|
||||
aurora_lights_.brightness = 180
|
||||
var sunset_glow_ = animation.rich_palette_animation(engine)
|
||||
sunset_glow_.palette = animation.global('sunset_sky_', 'sunset_sky')
|
||||
sunset_glow_.cycle_period = 6000
|
||||
sunset_glow_.transition_type = animation.global('SINE_', 'SINE')
|
||||
sunset_glow_.brightness = 220
|
||||
# Sequence to showcase all palettes
|
||||
def sequence_palette_showcase()
|
||||
var steps = []
|
||||
|
||||
@ -1,125 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: pattern_animation_demo.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Unified Pattern-Animation Demo
|
||||
# # This DSL example demonstrates the new unified architecture where Animation extends Pattern
|
||||
#
|
||||
# strip length 30
|
||||
#
|
||||
# # UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
|
||||
# # No more artificial distinction between patterns and animations
|
||||
# animation solid_red = solid(red) # Animation: solid red (infinite duration)
|
||||
# animation solid_blue = solid(blue) # Animation: solid blue (infinite duration)
|
||||
# animation solid_green = solid(green) # Animation: solid green (infinite duration)
|
||||
#
|
||||
# # COMPOSITION: Animations can use other animations as base
|
||||
# animation pulsing_red = pulse(solid_red, 0%, 100%, 2s) # Animation using animation
|
||||
# animation pulsing_blue = pulse(solid_blue, 50%, 100%, 1s) # Animation using animation
|
||||
# animation pulsing_green = pulse(solid_green, 20%, 80%, 3s) # Animation using animation
|
||||
#
|
||||
# # Set priorities (all animations inherit from Pattern)
|
||||
# solid_red.priority = 0 # Base animation priority
|
||||
# pulsing_red.priority = 10 # Higher priority
|
||||
# pulsing_blue.priority = 20 # Even higher priority
|
||||
# pulsing_green.priority = 5 # Medium priority
|
||||
#
|
||||
# # Set opacity (all animations inherit from Pattern)
|
||||
# solid_red.opacity = 255 # Full opacity
|
||||
# pulsing_red.opacity = 200 # Slightly dimmed
|
||||
# pulsing_blue.opacity = 150 # More dimmed
|
||||
#
|
||||
# # RECURSIVE COMPOSITION: Animations can use other animations!
|
||||
# # This creates infinitely composable effects
|
||||
# animation complex_pulse = pulse(pulsing_red, 30%, 70%, 4s) # Pulse a pulsing animation!
|
||||
#
|
||||
# # Create a sequence that demonstrates the unified architecture
|
||||
# sequence unified_demo {
|
||||
# # All animations can be used directly in sequences
|
||||
# play solid_red for 2s # Use solid animation
|
||||
# wait 500ms
|
||||
#
|
||||
# # Composed animations work seamlessly
|
||||
# play pulsing_red for 3s # Use pulse animation
|
||||
# wait 500ms
|
||||
#
|
||||
# play pulsing_blue for 2s # Use another pulse animation
|
||||
# wait 500ms
|
||||
#
|
||||
# # Show recursive composition - animation using animation
|
||||
# play complex_pulse for 4s # Nested animation effect
|
||||
# wait 500ms
|
||||
#
|
||||
# # Show that all animations support the same properties
|
||||
# repeat 2 times:
|
||||
# play solid_green for 1s # Animation with priority/opacity
|
||||
# play pulsing_green for 2s # Animation with priority/opacity
|
||||
# wait 500ms
|
||||
# }
|
||||
#
|
||||
# # Run the demonstration
|
||||
# run unified_demo
|
||||
|
||||
import animation
|
||||
|
||||
# Unified Pattern-Animation Demo
|
||||
# This DSL example demonstrates the new unified architecture where Animation extends Pattern
|
||||
var engine = animation.init_strip(30)
|
||||
# UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
|
||||
# No more artificial distinction between patterns and animations
|
||||
var solid_red_ = animation.solid(0xFFFF0000) # Animation: solid red (infinite duration)
|
||||
var solid_blue_ = animation.solid(0xFF0000FF) # Animation: solid blue (infinite duration)
|
||||
var solid_green_ = animation.solid(0xFF008000) # Animation: solid green (infinite duration)
|
||||
# COMPOSITION: Animations can use other animations as base
|
||||
var pulsing_red_ = animation.pulse(animation.global('solid_red_', 'solid_red'), 0, 255, 2000) # Animation using animation
|
||||
var pulsing_blue_ = animation.pulse(animation.global('solid_blue_', 'solid_blue'), 127, 255, 1000) # Animation using animation
|
||||
var pulsing_green_ = animation.pulse(animation.global('solid_green_', 'solid_green'), 51, 204, 3000) # Animation using animation
|
||||
# Set priorities (all animations inherit from Pattern)
|
||||
animation.global('solid_red_').priority = 0 # Base animation priority
|
||||
animation.global('pulsing_red_').priority = 10 # Higher priority
|
||||
animation.global('pulsing_blue_').priority = 20 # Even higher priority
|
||||
animation.global('pulsing_green_').priority = 5 # Medium priority
|
||||
# Set opacity (all animations inherit from Pattern)
|
||||
animation.global('solid_red_').opacity = 255 # Full opacity
|
||||
animation.global('pulsing_red_').opacity = 200 # Slightly dimmed
|
||||
animation.global('pulsing_blue_').opacity = 150 # More dimmed
|
||||
# RECURSIVE COMPOSITION: Animations can use other animations!
|
||||
# This creates infinitely composable effects
|
||||
var complex_pulse_ = animation.pulse(animation.global('pulsing_red_', 'pulsing_red'), 76, 178, 4000) # Pulse a pulsing animation!
|
||||
# Create a sequence that demonstrates the unified architecture
|
||||
def sequence_unified_demo()
|
||||
var steps = []
|
||||
# All animations can be used directly in sequences
|
||||
steps.push(animation.create_play_step(animation.global('solid_red_'), 2000)) # Use solid animation
|
||||
steps.push(animation.create_wait_step(500))
|
||||
# Composed animations work seamlessly
|
||||
steps.push(animation.create_play_step(animation.global('pulsing_red_'), 3000)) # Use pulse animation
|
||||
steps.push(animation.create_wait_step(500))
|
||||
steps.push(animation.create_play_step(animation.global('pulsing_blue_'), 2000)) # Use another pulse animation
|
||||
steps.push(animation.create_wait_step(500))
|
||||
# Show recursive composition - animation using animation
|
||||
steps.push(animation.create_play_step(animation.global('complex_pulse_'), 4000)) # Nested animation effect
|
||||
steps.push(animation.create_wait_step(500))
|
||||
# Show that all animations support the same properties
|
||||
for repeat_i : 0..2-1
|
||||
steps.push(animation.create_play_step(animation.global('solid_green_'), 1000)) # Animation with priority/opacity
|
||||
steps.push(animation.create_play_step(animation.global('pulsing_green_'), 2000)) # Animation with priority/opacity
|
||||
steps.push(animation.create_wait_step(500))
|
||||
end
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end
|
||||
# Run the demonstration
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_unified_demo')
|
||||
var seq_manager = global.sequence_unified_demo()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('unified_demo_'))
|
||||
end
|
||||
engine.start()
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: plasma_wave.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Plasma Wave - Smooth flowing plasma colors
|
||||
# # Continuous color waves like plasma display
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define plasma color palette with smooth transitions
|
||||
# palette plasma_colors = [
|
||||
@ -22,41 +22,41 @@
|
||||
# ]
|
||||
#
|
||||
# # Base plasma animation with medium speed
|
||||
# animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 200)
|
||||
# animation plasma_base = rich_palette_animation(palette=plasma_colors, cycle_period=6s, transition_type=SINE, brightness=200)
|
||||
#
|
||||
# # Add multiple wave layers for complexity
|
||||
# pattern wave1_pattern = rich_palette_color_provider(plasma_colors, 4s, smooth, 255)
|
||||
# animation plasma_wave1 = pulse_position_animation(
|
||||
# wave1_pattern, # color source
|
||||
# 0, # initial position
|
||||
# 20, # wide wave
|
||||
# 10 # very smooth
|
||||
# color wave1_pattern = rich_palette(palette=plasma_colors, cycle_period=4s, transition_type=SINE, brightness=255)
|
||||
# animation plasma_wave1 = beacon_animation(
|
||||
# color=wave1_pattern, # color source
|
||||
# pos=0, # initial position
|
||||
# beacon_size=20, # wide wave
|
||||
# slew_size=10 # very smooth
|
||||
# )
|
||||
# plasma_wave1.priority = 10
|
||||
# plasma_wave1.pos = smooth(0, 40, 8s)
|
||||
# plasma_wave1.pos = smooth(min_value=0, max_value=40, duration=8s)
|
||||
#
|
||||
# pattern wave2_pattern = rich_palette_color_provider(plasma_colors, 5s, smooth, 180)
|
||||
# animation plasma_wave2 = pulse_position_animation(
|
||||
# wave2_pattern, # color source
|
||||
# 45, # initial position
|
||||
# 15, # medium wave
|
||||
# 8 # smooth
|
||||
# color wave2_pattern = rich_palette(palette=plasma_colors, cycle_period=5s, transition_type=SINE, brightness=180)
|
||||
# animation plasma_wave2 = beacon_animation(
|
||||
# color=wave2_pattern, # color source
|
||||
# pos=45, # initial position
|
||||
# beacon_size=15, # medium wave
|
||||
# slew_size=8 # smooth
|
||||
# )
|
||||
# plasma_wave2.priority = 8
|
||||
# plasma_wave2.pos = smooth(45, 15, 10s) # Opposite direction
|
||||
# plasma_wave2.pos = smooth(min_value=45, max_value=15, duration=10s) # Opposite direction
|
||||
#
|
||||
# pattern wave3_pattern = rich_palette_color_provider(plasma_colors, 3s, smooth, 220)
|
||||
# animation plasma_wave3 = pulse_position_animation(
|
||||
# wave3_pattern, # color source
|
||||
# 20, # initial position
|
||||
# 12, # smaller wave
|
||||
# 6 # smooth
|
||||
# color wave3_pattern = rich_palette(palette=plasma_colors, cycle_period=3s, transition_type=SINE, brightness=220)
|
||||
# animation plasma_wave3 = beacon_animation(
|
||||
# color=wave3_pattern, # color source
|
||||
# pos=20, # initial position
|
||||
# beacon_size=12, # smaller wave
|
||||
# slew_size=6 # smooth
|
||||
# )
|
||||
# plasma_wave3.priority = 12
|
||||
# plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
|
||||
# plasma_wave3.pos = smooth(min_value=20, max_value=50, duration=6s) # Different speed
|
||||
#
|
||||
# # Add subtle intensity variation
|
||||
# plasma_base.opacity = smooth(150, 255, 12s)
|
||||
# plasma_base.opacity = smooth(min_value=150, max_value=255, duration=12s)
|
||||
#
|
||||
# # Start all animations
|
||||
# run plasma_base
|
||||
@ -68,26 +68,73 @@ import animation
|
||||
|
||||
# Plasma Wave - Smooth flowing plasma colors
|
||||
# Continuous color waves like plasma display
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define plasma color palette with smooth transitions
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var plasma_colors_ = bytes("00FF0080" "33FF8000" "66FFFF00" "9980FF00" "CC00FF80" "FF0080FF")
|
||||
# Base plasma animation with medium speed
|
||||
var plasma_base_ = animation.rich_palette_animation(animation.global('plasma_colors_', 'plasma_colors'), 6000, animation.global('smooth_', 'smooth'), 200)
|
||||
var plasma_base_ = animation.rich_palette_animation(engine)
|
||||
plasma_base_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
plasma_base_.cycle_period = 6000
|
||||
plasma_base_.transition_type = animation.global('SINE_', 'SINE')
|
||||
plasma_base_.brightness = 200
|
||||
# Add multiple wave layers for complexity
|
||||
var wave1_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 4000, animation.global('smooth_', 'smooth'), 255)
|
||||
var plasma_wave1_ = animation.pulse_position_animation(animation.global('wave1_pattern_', 'wave1_pattern'), 0, 20, 10)
|
||||
var wave1_pattern_ = animation.rich_palette(engine)
|
||||
wave1_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave1_pattern_.cycle_period = 4000
|
||||
wave1_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave1_pattern_.brightness = 255
|
||||
var plasma_wave1_ = animation.beacon_animation(engine)
|
||||
plasma_wave1_.color = animation.global('wave1_pattern_', 'wave1_pattern')
|
||||
plasma_wave1_.pos = 0
|
||||
plasma_wave1_.beacon_size = 20
|
||||
plasma_wave1_.slew_size = 10 # very smooth
|
||||
animation.global('plasma_wave1_').priority = 10
|
||||
animation.global('plasma_wave1_').pos = animation.smooth(0, 40, 8000)
|
||||
var wave2_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 5000, animation.global('smooth_', 'smooth'), 180)
|
||||
var plasma_wave2_ = animation.pulse_position_animation(animation.global('wave2_pattern_', 'wave2_pattern'), 45, 15, 8)
|
||||
var temp_smooth_156 = animation.smooth(engine)
|
||||
temp_smooth_156.min_value = 0
|
||||
temp_smooth_156.max_value = 40
|
||||
temp_smooth_156.duration = 8000
|
||||
animation.global('plasma_wave1_').pos = temp_smooth_156
|
||||
var wave2_pattern_ = animation.rich_palette(engine)
|
||||
wave2_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave2_pattern_.cycle_period = 5000
|
||||
wave2_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave2_pattern_.brightness = 180
|
||||
var plasma_wave2_ = animation.beacon_animation(engine)
|
||||
plasma_wave2_.color = animation.global('wave2_pattern_', 'wave2_pattern')
|
||||
plasma_wave2_.pos = 45
|
||||
plasma_wave2_.beacon_size = 15
|
||||
plasma_wave2_.slew_size = 8 # smooth
|
||||
animation.global('plasma_wave2_').priority = 8
|
||||
animation.global('plasma_wave2_').pos = animation.smooth(45, 15, 10000) # Opposite direction
|
||||
var wave3_pattern_ = animation.rich_palette_color_provider(animation.global('plasma_colors_', 'plasma_colors'), 3000, animation.global('smooth_', 'smooth'), 220)
|
||||
var plasma_wave3_ = animation.pulse_position_animation(animation.global('wave3_pattern_', 'wave3_pattern'), 20, 12, 6)
|
||||
var temp_smooth_235 = animation.smooth(engine)
|
||||
temp_smooth_235.min_value = 45
|
||||
temp_smooth_235.max_value = 15
|
||||
temp_smooth_235.duration = 10000
|
||||
animation.global('plasma_wave2_').pos = temp_smooth_235 # Opposite direction
|
||||
var wave3_pattern_ = animation.rich_palette(engine)
|
||||
wave3_pattern_.palette = animation.global('plasma_colors_', 'plasma_colors')
|
||||
wave3_pattern_.cycle_period = 3000
|
||||
wave3_pattern_.transition_type = animation.global('SINE_', 'SINE')
|
||||
wave3_pattern_.brightness = 220
|
||||
var plasma_wave3_ = animation.beacon_animation(engine)
|
||||
plasma_wave3_.color = animation.global('wave3_pattern_', 'wave3_pattern')
|
||||
plasma_wave3_.pos = 20
|
||||
plasma_wave3_.beacon_size = 12
|
||||
plasma_wave3_.slew_size = 6 # smooth
|
||||
animation.global('plasma_wave3_').priority = 12
|
||||
animation.global('plasma_wave3_').pos = animation.smooth(20, 50, 6000) # Different speed
|
||||
var temp_smooth_315 = animation.smooth(engine)
|
||||
temp_smooth_315.min_value = 20
|
||||
temp_smooth_315.max_value = 50
|
||||
temp_smooth_315.duration = 6000
|
||||
animation.global('plasma_wave3_').pos = temp_smooth_315 # Different speed
|
||||
# Add subtle intensity variation
|
||||
animation.global('plasma_base_').opacity = animation.smooth(150, 255, 12000)
|
||||
var temp_smooth_338 = animation.smooth(engine)
|
||||
temp_smooth_338.min_value = 150
|
||||
temp_smooth_338.max_value = 255
|
||||
temp_smooth_338.duration = 12000
|
||||
animation.global('plasma_base_').opacity = temp_smooth_338
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_plasma_base')
|
||||
|
||||
@ -2,41 +2,41 @@
|
||||
# Source: police_lights.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Police Lights - Red and blue alternating flashes
|
||||
# # Emergency vehicle style lighting
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define zones for left and right halves
|
||||
# set half_length = 30
|
||||
#
|
||||
# # Left side red flashing
|
||||
# animation left_red = pulse_position_animation(
|
||||
# 0xFF0000, # Bright red
|
||||
# 15, # center of left half
|
||||
# 15, # half the strip
|
||||
# 2 # sharp edges
|
||||
# animation left_red = beacon_animation(
|
||||
# color=0xFF0000, # Bright red
|
||||
# pos=15, # center of left half
|
||||
# beacon_size=15, # half the strip
|
||||
# slew_size=2 # sharp edges
|
||||
# )
|
||||
# left_red.priority = 10
|
||||
# left_red.opacity = square(0, 255, 400ms, 50) # 50% duty cycle
|
||||
# left_red.opacity = square(min_value=0, max_value=255, duration=400ms, duty_cycle=50) # 50% duty cycle
|
||||
#
|
||||
# # Right side blue flashing (opposite phase)
|
||||
# animation right_blue = pulse_position_animation(
|
||||
# 0x0000FF, # Bright blue
|
||||
# 45, # center of right half
|
||||
# 15, # half the strip
|
||||
# 2 # sharp edges
|
||||
# animation right_blue = beacon_animation(
|
||||
# color=0x0000FF, # Bright blue
|
||||
# pos=45, # center of right half
|
||||
# beacon_size=15, # half the strip
|
||||
# slew_size=2 # sharp edges
|
||||
# )
|
||||
# right_blue.priority = 10
|
||||
# right_blue.opacity = square(255, 0, 400ms, 50) # Opposite phase
|
||||
# right_blue.opacity = square(min_value=255, max_value=0, duration=400ms, duty_cycle=50) # Opposite phase
|
||||
#
|
||||
# # Add white strobe overlay occasionally
|
||||
# animation white_strobe = solid(0xFFFFFF)
|
||||
# white_strobe.opacity = square(0, 255, 100ms, 5) # Quick bright flashes
|
||||
# animation white_strobe = solid(color=0xFFFFFF)
|
||||
# white_strobe.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=5) # Quick bright flashes
|
||||
# white_strobe.priority = 20
|
||||
#
|
||||
# # Start all animations
|
||||
@ -48,20 +48,47 @@ import animation
|
||||
|
||||
# Police Lights - Red and blue alternating flashes
|
||||
# Emergency vehicle style lighting
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define zones for left and right halves
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var half_length_ = 30
|
||||
# Left side red flashing
|
||||
var left_red_ = animation.pulse_position_animation(0xFFFF0000, 15, 15, 2)
|
||||
var left_red_ = animation.beacon_animation(engine)
|
||||
left_red_.color = 0xFFFF0000
|
||||
left_red_.pos = 15
|
||||
left_red_.beacon_size = 15
|
||||
left_red_.slew_size = 2 # sharp edges
|
||||
animation.global('left_red_').priority = 10
|
||||
animation.global('left_red_').opacity = animation.square(0, 255, 400, 50) # 50% duty cycle
|
||||
var temp_square_60 = animation.square(engine)
|
||||
temp_square_60.min_value = 0
|
||||
temp_square_60.max_value = 255
|
||||
temp_square_60.duration = 400
|
||||
temp_square_60.duty_cycle = 50
|
||||
animation.global('left_red_').opacity = temp_square_60 # 50% duty cycle
|
||||
# Right side blue flashing (opposite phase)
|
||||
var right_blue_ = animation.pulse_position_animation(0xFF0000FF, 45, 15, 2)
|
||||
var right_blue_ = animation.beacon_animation(engine)
|
||||
right_blue_.color = 0xFF0000FF
|
||||
right_blue_.pos = 45
|
||||
right_blue_.beacon_size = 15
|
||||
right_blue_.slew_size = 2 # sharp edges
|
||||
animation.global('right_blue_').priority = 10
|
||||
animation.global('right_blue_').opacity = animation.square(255, 0, 400, 50) # Opposite phase
|
||||
var temp_square_124 = animation.square(engine)
|
||||
temp_square_124.min_value = 255
|
||||
temp_square_124.max_value = 0
|
||||
temp_square_124.duration = 400
|
||||
temp_square_124.duty_cycle = 50
|
||||
animation.global('right_blue_').opacity = temp_square_124 # Opposite phase
|
||||
# Add white strobe overlay occasionally
|
||||
var white_strobe_ = animation.solid(0xFFFFFFFF)
|
||||
animation.global('white_strobe_').opacity = animation.square(0, 255, 100, 5) # Quick bright flashes
|
||||
var white_strobe_ = animation.solid(engine)
|
||||
white_strobe_.color = 0xFFFFFFFF
|
||||
var temp_square_161 = animation.square(engine)
|
||||
temp_square_161.min_value = 0
|
||||
temp_square_161.max_value = 255
|
||||
temp_square_161.duration = 100
|
||||
temp_square_161.duty_cycle = 5
|
||||
animation.global('white_strobe_').opacity = temp_square_161 # Quick bright flashes
|
||||
animation.global('white_strobe_').priority = 20
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: property_assignment_demo.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Property Assignment Demo
|
||||
# # Shows how to set animation properties after creation
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define colors
|
||||
# color red_custom = 0xFF0000
|
||||
@ -17,12 +17,12 @@
|
||||
# color green_custom = 0x00FF00
|
||||
#
|
||||
# # Create animations
|
||||
# animation left_pulse = pulse_position_animation(red_custom, 15, 15, 3)
|
||||
# animation center_pulse = pulse_position_animation(blue_custom, 30, 15, 3)
|
||||
# animation right_pulse = pulse_position_animation(green_custom, 45, 15, 3)
|
||||
# animation left_pulse = beacon_animation(color=red_custom, pos=15, beacon_size=15, slew_size=3)
|
||||
# animation center_pulse = beacon_animation(color=blue_custom, pos=30, beacon_size=15, slew_size=3)
|
||||
# animation right_pulse = beacon_animation(color=green_custom, pos=45, beacon_size=15, slew_size=3)
|
||||
#
|
||||
# # Set different opacities
|
||||
# left_pulse.opacity = 255 # Full brightness
|
||||
# left_pulse.opacity = 255 # Full slew_size
|
||||
# center_pulse.opacity = 200 # Slightly dimmed
|
||||
# right_pulse.opacity = 150 # More dimmed
|
||||
#
|
||||
@ -54,17 +54,32 @@ import animation
|
||||
|
||||
# Property Assignment Demo
|
||||
# Shows how to set animation properties after creation
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var red_custom_ = 0xFFFF0000
|
||||
var blue_custom_ = 0xFF0000FF
|
||||
var green_custom_ = 0xFF00FF00
|
||||
# Create animations
|
||||
var left_pulse_ = animation.pulse_position_animation(animation.global('red_custom_', 'red_custom'), 15, 15, 3)
|
||||
var center_pulse_ = animation.pulse_position_animation(animation.global('blue_custom_', 'blue_custom'), 30, 15, 3)
|
||||
var right_pulse_ = animation.pulse_position_animation(animation.global('green_custom_', 'green_custom'), 45, 15, 3)
|
||||
var left_pulse_ = animation.beacon_animation(engine)
|
||||
left_pulse_.color = animation.global('red_custom_', 'red_custom')
|
||||
left_pulse_.pos = 15
|
||||
left_pulse_.beacon_size = 15
|
||||
left_pulse_.slew_size = 3
|
||||
var center_pulse_ = animation.beacon_animation(engine)
|
||||
center_pulse_.color = animation.global('blue_custom_', 'blue_custom')
|
||||
center_pulse_.pos = 30
|
||||
center_pulse_.beacon_size = 15
|
||||
center_pulse_.slew_size = 3
|
||||
var right_pulse_ = animation.beacon_animation(engine)
|
||||
right_pulse_.color = animation.global('green_custom_', 'green_custom')
|
||||
right_pulse_.pos = 45
|
||||
right_pulse_.beacon_size = 15
|
||||
right_pulse_.slew_size = 3
|
||||
# Set different opacities
|
||||
animation.global('left_pulse_').opacity = 255 # Full brightness
|
||||
animation.global('left_pulse_').opacity = 255 # Full slew_size
|
||||
animation.global('center_pulse_').opacity = 200 # Slightly dimmed
|
||||
animation.global('right_pulse_').opacity = 150 # More dimmed
|
||||
# Set priorities (higher numbers have priority)
|
||||
|
||||
@ -2,37 +2,45 @@
|
||||
# Source: rainbow_cycle.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Rainbow Cycle - Classic WLED effect
|
||||
# # Smooth rainbow colors cycling across the strip
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Create smooth rainbow cycle animation
|
||||
# animation rainbow_cycle = color_cycle_animation(
|
||||
# [0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
|
||||
# 5s # cycle period
|
||||
# color rainbow_cycle = color_cycle(
|
||||
# palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
|
||||
# cycle_period=5s # cycle period
|
||||
# )
|
||||
# animation rainbow_animation = solid(color=rainbow_cycle)
|
||||
#
|
||||
# # Start the animation
|
||||
# run rainbow_cycle
|
||||
# run rainbow_animation
|
||||
|
||||
import animation
|
||||
|
||||
# Rainbow Cycle - Classic WLED effect
|
||||
# Smooth rainbow colors cycling across the strip
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Create smooth rainbow cycle animation
|
||||
var rainbow_cycle_ = animation.color_cycle_animation([0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF], 5000)
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_cycle_ = animation.color_cycle(engine)
|
||||
rainbow_cycle_.palette = [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF]
|
||||
rainbow_cycle_.cycle_period = 5000 # cycle period
|
||||
var rainbow_animation_ = animation.solid(engine)
|
||||
rainbow_animation_.color = animation.global('rainbow_cycle_', 'rainbow_cycle')
|
||||
# Start the animation
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_rainbow_cycle')
|
||||
var seq_manager = global.sequence_rainbow_cycle()
|
||||
if global.contains('sequence_rainbow_animation')
|
||||
var seq_manager = global.sequence_rainbow_animation()
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
else
|
||||
engine.add_animation(animation.global('rainbow_cycle_'))
|
||||
engine.add_animation(animation.global('rainbow_animation_'))
|
||||
end
|
||||
engine.start()
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env berry
|
||||
# Test runner for compiled DSL examples
|
||||
# Generated automatically by compile_dsl_examples.be
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add animation library path
|
||||
sys.path().push("lib/libesp32/berry_animation")
|
||||
|
||||
# Import animation framework
|
||||
import animation
|
||||
|
||||
def run_compiled_example(filename)
|
||||
print(f"Running {filename}...")
|
||||
try
|
||||
var f = open(f"anim_examples/compiled/{filename}", "r")
|
||||
var code = f.read()
|
||||
f.close()
|
||||
|
||||
var compiled_func = compile(code)
|
||||
if compiled_func != nil
|
||||
compiled_func()
|
||||
print(f" ✓ {filename} executed successfully")
|
||||
return true
|
||||
else
|
||||
print(f" ✗ {filename} failed to compile")
|
||||
return false
|
||||
end
|
||||
except .. as e, msg
|
||||
print(f" ✗ {filename} execution failed: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def run_all_examples()
|
||||
var files = os.listdir("compiled")
|
||||
var success_count = 0
|
||||
var total_count = 0
|
||||
|
||||
for file : files
|
||||
if string.endswith(file, ".be")
|
||||
total_count += 1
|
||||
if run_compiled_example(file)
|
||||
success_count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print(f"\nTest Results: {success_count}/{total_count} examples ran successfully")
|
||||
end
|
||||
|
||||
# Run all examples if script is executed directly
|
||||
run_all_examples()
|
||||
@ -2,40 +2,41 @@
|
||||
# Source: scanner_larson.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Scanner (Larson) - Knight Rider style scanner
|
||||
# # Red dot bouncing back and forth
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark background
|
||||
# color scanner_bg = 0x110000
|
||||
# animation background = solid(scanner_bg)
|
||||
# animation background = solid(color=scanner_bg)
|
||||
#
|
||||
# # Main scanner pulse that bounces
|
||||
# animation scanner = pulse_position_animation(
|
||||
# 0xFF0000, # Bright red
|
||||
# 2, # initial position
|
||||
# 3, # pulse width
|
||||
# 2 # fade region
|
||||
# animation scanner = beacon_animation(
|
||||
# color=0xFF0000, # Bright red
|
||||
# pos=2, # initial position
|
||||
# beacon_size=3, # pulse width
|
||||
# slew_size=2 # fade region
|
||||
# )
|
||||
# scanner.priority = 10
|
||||
#
|
||||
# # Bouncing position from left to right and back
|
||||
# scanner.pos = triangle(2, 57, 2s)
|
||||
# scanner.pos = triangle(min_value=2, max_value=57, duration=2s)
|
||||
#
|
||||
# # Add trailing glow effect
|
||||
# animation scanner_trail = pulse_position_animation(
|
||||
# 0x660000, # Dim red trail
|
||||
# 2, # initial position
|
||||
# 6, # wider trail
|
||||
# 4 # more fade
|
||||
# animation scanner_trail = beacon_animation(
|
||||
# color=0x660000, # Dim red trail
|
||||
# pos=2, # initial position
|
||||
# beacon_size=6, # wider trail
|
||||
# slew_size=4 # more fade
|
||||
# )
|
||||
# scanner_trail.priority = 5
|
||||
# scanner_trail.pos = triangle(2, 57, 2s)
|
||||
# set pos_test = triangle(min_value=2, max_value=57, duration=2s)
|
||||
# scanner_trail.pos = pos_test
|
||||
# scanner_trail.opacity = 128 # Half brightness
|
||||
#
|
||||
# # Start all animations
|
||||
@ -47,19 +48,40 @@ import animation
|
||||
|
||||
# Scanner (Larson) - Knight Rider style scanner
|
||||
# Red dot bouncing back and forth
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var scanner_bg_ = 0xFF110000
|
||||
var background_ = animation.solid(animation.global('scanner_bg_', 'scanner_bg'))
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('scanner_bg_', 'scanner_bg')
|
||||
# Main scanner pulse that bounces
|
||||
var scanner_ = animation.pulse_position_animation(0xFFFF0000, 2, 3, 2)
|
||||
var scanner_ = animation.beacon_animation(engine)
|
||||
scanner_.color = 0xFFFF0000
|
||||
scanner_.pos = 2
|
||||
scanner_.beacon_size = 3
|
||||
scanner_.slew_size = 2 # fade region
|
||||
animation.global('scanner_').priority = 10
|
||||
# Bouncing position from left to right and back
|
||||
animation.global('scanner_').pos = animation.triangle(2, 57, 2000)
|
||||
var temp_triangle_73 = animation.triangle(engine)
|
||||
temp_triangle_73.min_value = 2
|
||||
temp_triangle_73.max_value = 57
|
||||
temp_triangle_73.duration = 2000
|
||||
animation.global('scanner_').pos = temp_triangle_73
|
||||
# Add trailing glow effect
|
||||
var scanner_trail_ = animation.pulse_position_animation(0xFF660000, 2, 6, 4)
|
||||
var scanner_trail_ = animation.beacon_animation(engine)
|
||||
scanner_trail_.color = 0xFF660000
|
||||
scanner_trail_.pos = 2
|
||||
scanner_trail_.beacon_size = 6
|
||||
scanner_trail_.slew_size = 4 # more fade
|
||||
animation.global('scanner_trail_').priority = 5
|
||||
animation.global('scanner_trail_').pos = animation.triangle(2, 57, 2000)
|
||||
var temp_triangle_131 = animation.triangle(engine)
|
||||
temp_triangle_131.min_value = 2
|
||||
temp_triangle_131.max_value = 57
|
||||
temp_triangle_131.duration = 2000
|
||||
var pos_test_ = temp_triangle_131
|
||||
animation.global('scanner_trail_').pos = animation.global('pos_test_', 'pos_test')
|
||||
animation.global('scanner_trail_').opacity = 128 # Half brightness
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: simple_palette.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Simple Palette Example
|
||||
# # Demonstrates basic palette usage in the DSL
|
||||
#
|
||||
# strip length 20
|
||||
# #strip length 20
|
||||
#
|
||||
# # Define a simple rainbow palette
|
||||
# palette rainbow = [
|
||||
@ -21,7 +21,7 @@
|
||||
# ]
|
||||
#
|
||||
# # Create an animation using the palette
|
||||
# animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
|
||||
# animation rainbow_cycle = rich_palette_animation(palette=rainbow, cycle_period=3s)
|
||||
#
|
||||
# # Simple sequence
|
||||
# sequence demo {
|
||||
@ -34,11 +34,16 @@ import animation
|
||||
|
||||
# Simple Palette Example
|
||||
# Demonstrates basic palette usage in the DSL
|
||||
var engine = animation.init_strip(20)
|
||||
#strip length 20
|
||||
# Define a simple rainbow palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_ = bytes("00FF0000" "40FFA500" "80FFFF00" "C0008000" "FF0000FF")
|
||||
# Create an animation using the palette
|
||||
var rainbow_cycle_ = animation.rich_palette_animation(animation.global('rainbow_', 'rainbow'), 3000)
|
||||
var rainbow_cycle_ = animation.rich_palette_animation(engine)
|
||||
rainbow_cycle_.palette = animation.global('rainbow_', 'rainbow')
|
||||
rainbow_cycle_.cycle_period = 3000
|
||||
# Simple sequence
|
||||
def sequence_demo()
|
||||
var steps = []
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
# Source: sunrise_sunset.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Sunrise Sunset - Warm color transition
|
||||
# # Gradual transition from night to day colors
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Define time-of-day color palette
|
||||
# palette daylight_colors = [
|
||||
@ -25,38 +25,38 @@
|
||||
# ]
|
||||
#
|
||||
# # Main daylight cycle - very slow transition
|
||||
# animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
|
||||
# animation daylight_cycle = rich_palette_animation(palette=daylight_colors, cycle_period=60s)
|
||||
#
|
||||
# # Add sun position effect - bright spot that moves
|
||||
# animation sun_position = pulse_position_animation(
|
||||
# 0xFFFFAA, # Bright yellow sun
|
||||
# 5, # initial position
|
||||
# 8, # sun size
|
||||
# 4 # soft glow
|
||||
# animation sun_position = beacon_animation(
|
||||
# color=0xFFFFAA, # Bright yellow sun
|
||||
# pos=5, # initial position
|
||||
# beacon_size=8, # sun size
|
||||
# slew_size=4 # soft glow
|
||||
# )
|
||||
# sun_position.priority = 10
|
||||
# sun_position.pos = smooth(5, 55, 30s) # Sun arc across sky
|
||||
# sun_position.opacity = smooth(0, 255, 30s) # Fade in and out
|
||||
# sun_position.pos = smooth(min_value=5, max_value=55, duration=30s) # Sun arc across sky
|
||||
# sun_position.opacity = smooth(min_value=0, max_value=255, duration=30s) # Fade in and out
|
||||
#
|
||||
# # Add atmospheric glow around sun
|
||||
# animation sun_glow = pulse_position_animation(
|
||||
# 0xFFCC88, # Warm glow
|
||||
# 5, # initial position
|
||||
# 16, # larger glow
|
||||
# 8 # very soft
|
||||
# animation sun_glow = beacon_animation(
|
||||
# color=0xFFCC88, # Warm glow
|
||||
# pos=5, # initial position
|
||||
# beacon_size=16, # larger glow
|
||||
# slew_size=8 # very soft
|
||||
# )
|
||||
# sun_glow.priority = 5
|
||||
# sun_glow.pos = smooth(5, 55, 30s) # Follow sun
|
||||
# sun_glow.opacity = smooth(0, 150, 30s) # Dimmer glow
|
||||
# sun_glow.pos = smooth(min_value=5, max_value=55, duration=30s) # Follow sun
|
||||
# sun_glow.opacity = smooth(min_value=0, max_value=150, duration=30s) # Dimmer glow
|
||||
#
|
||||
# # Add twinkling stars during night phases
|
||||
# animation stars = twinkle_animation(
|
||||
# 0xFFFFFF, # White stars
|
||||
# 6, # density (star count)
|
||||
# 1s # twinkle speed (slow twinkle)
|
||||
# color=0xFFFFFF, # White stars
|
||||
# density=6, # density (star count)
|
||||
# twinkle_speed=1s # twinkle speed (slow twinkle)
|
||||
# )
|
||||
# stars.priority = 15
|
||||
# stars.opacity = smooth(255, 0, 30s) # Fade out during day
|
||||
# stars.opacity = smooth(min_value=255, max_value=0, duration=30s) # Fade out during day
|
||||
#
|
||||
# # Start all animations
|
||||
# run daylight_cycle
|
||||
@ -68,25 +68,61 @@ import animation
|
||||
|
||||
# Sunrise Sunset - Warm color transition
|
||||
# Gradual transition from night to day colors
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Define time-of-day color palette
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var daylight_colors_ = bytes("00000011" "20001133" "40FF4400" "60FFAA00" "80FFFF88" "A0FFAA44" "C0FF6600" "E0AA2200" "FF220011")
|
||||
# Main daylight cycle - very slow transition
|
||||
var daylight_cycle_ = animation.rich_palette_animation(animation.global('daylight_colors_', 'daylight_colors'), 60000)
|
||||
var daylight_cycle_ = animation.rich_palette_animation(engine)
|
||||
daylight_cycle_.palette = animation.global('daylight_colors_', 'daylight_colors')
|
||||
daylight_cycle_.cycle_period = 60000
|
||||
# Add sun position effect - bright spot that moves
|
||||
var sun_position_ = animation.pulse_position_animation(0xFFFFFFAA, 5, 8, 4)
|
||||
var sun_position_ = animation.beacon_animation(engine)
|
||||
sun_position_.color = 0xFFFFFFAA
|
||||
sun_position_.pos = 5
|
||||
sun_position_.beacon_size = 8
|
||||
sun_position_.slew_size = 4 # soft glow
|
||||
animation.global('sun_position_').priority = 10
|
||||
animation.global('sun_position_').pos = animation.smooth(5, 55, 30000) # Sun arc across sky
|
||||
animation.global('sun_position_').opacity = animation.smooth(0, 255, 30000) # Fade in and out
|
||||
var temp_smooth_150 = animation.smooth(engine)
|
||||
temp_smooth_150.min_value = 5
|
||||
temp_smooth_150.max_value = 55
|
||||
temp_smooth_150.duration = 30000
|
||||
animation.global('sun_position_').pos = temp_smooth_150 # Sun arc across sky
|
||||
var temp_smooth_170 = animation.smooth(engine)
|
||||
temp_smooth_170.min_value = 0
|
||||
temp_smooth_170.max_value = 255
|
||||
temp_smooth_170.duration = 30000
|
||||
animation.global('sun_position_').opacity = temp_smooth_170 # Fade in and out
|
||||
# Add atmospheric glow around sun
|
||||
var sun_glow_ = animation.pulse_position_animation(0xFFFFCC88, 5, 16, 8)
|
||||
var sun_glow_ = animation.beacon_animation(engine)
|
||||
sun_glow_.color = 0xFFFFCC88
|
||||
sun_glow_.pos = 5
|
||||
sun_glow_.beacon_size = 16
|
||||
sun_glow_.slew_size = 8 # very soft
|
||||
animation.global('sun_glow_').priority = 5
|
||||
animation.global('sun_glow_').pos = animation.smooth(5, 55, 30000) # Follow sun
|
||||
animation.global('sun_glow_').opacity = animation.smooth(0, 150, 30000) # Dimmer glow
|
||||
var temp_smooth_230 = animation.smooth(engine)
|
||||
temp_smooth_230.min_value = 5
|
||||
temp_smooth_230.max_value = 55
|
||||
temp_smooth_230.duration = 30000
|
||||
animation.global('sun_glow_').pos = temp_smooth_230 # Follow sun
|
||||
var temp_smooth_250 = animation.smooth(engine)
|
||||
temp_smooth_250.min_value = 0
|
||||
temp_smooth_250.max_value = 150
|
||||
temp_smooth_250.duration = 30000
|
||||
animation.global('sun_glow_').opacity = temp_smooth_250 # Dimmer glow
|
||||
# Add twinkling stars during night phases
|
||||
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 6, 1000)
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFFFFFFF
|
||||
stars_.density = 6
|
||||
stars_.twinkle_speed = 1000 # twinkle speed (slow twinkle)
|
||||
animation.global('stars_').priority = 15
|
||||
animation.global('stars_').opacity = animation.smooth(255, 0, 30000) # Fade out during day
|
||||
var temp_smooth_304 = animation.smooth(engine)
|
||||
temp_smooth_304.min_value = 255
|
||||
temp_smooth_304.max_value = 0
|
||||
temp_smooth_304.duration = 30000
|
||||
animation.global('stars_').opacity = temp_smooth_304 # Fade out during day
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
if global.contains('sequence_daylight_cycle')
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env berry
|
||||
# Simple test runner for working DSL examples
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path().push("lib/libesp32/berry_animation")
|
||||
import animation
|
||||
|
||||
def test_compiled_file(filename)
|
||||
print(f"Testing {filename}...")
|
||||
try
|
||||
var f = open(f"anim_examples/compiled/{filename}", "r")
|
||||
var code = f.read()
|
||||
f.close()
|
||||
|
||||
# Try to compile the Berry code
|
||||
var compiled_func = compile(code)
|
||||
if compiled_func != nil
|
||||
print(f" ✓ {filename} compiles successfully")
|
||||
return true
|
||||
else
|
||||
print(f" ✗ {filename} failed to compile")
|
||||
return false
|
||||
end
|
||||
except .. as e, msg
|
||||
print(f" ✗ {filename} test failed: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Test all .be files in compiled directory
|
||||
var files = os.listdir("compiled")
|
||||
var success_count = 0
|
||||
var total_count = 0
|
||||
|
||||
for file : files
|
||||
import string
|
||||
if string.endswith(file, ".be")
|
||||
total_count += 1
|
||||
if test_compiled_file(file)
|
||||
success_count += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print(f"\nResults: {success_count}/{total_count} files compiled successfully")
|
||||
@ -2,32 +2,32 @@
|
||||
# Source: twinkle_stars.anim
|
||||
# Generated automatically
|
||||
#
|
||||
# This file was automatically generated by compile_all_dsl_examples.sh
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
# Original DSL source:
|
||||
# # Twinkle Stars - Random sparkling white stars
|
||||
# # White sparkles on dark blue background
|
||||
#
|
||||
# strip length 60
|
||||
# #strip length 60
|
||||
#
|
||||
# # Dark blue background
|
||||
# color night_sky = 0x000033
|
||||
# animation background = solid(night_sky)
|
||||
# animation background = solid(color=night_sky)
|
||||
#
|
||||
# # White twinkling stars
|
||||
# animation stars = twinkle_animation(
|
||||
# 0xFFFFFF, # White stars
|
||||
# 8, # density (number of stars)
|
||||
# 500ms # twinkle speed (twinkle duration)
|
||||
# color=0xFFFFFF, # White stars
|
||||
# density=8, # density (number of stars)
|
||||
# twinkle_speed=500ms # twinkle speed (twinkle duration)
|
||||
# )
|
||||
# stars.priority = 10
|
||||
#
|
||||
# # Add occasional bright flash
|
||||
# animation bright_flash = twinkle_animation(
|
||||
# 0xFFFFAA, # Bright yellow-white
|
||||
# 2, # density (fewer bright flashes)
|
||||
# 300ms # twinkle speed (quick flash)
|
||||
# color=0xFFFFAA, # Bright yellow-white
|
||||
# density=2, # density (fewer bright flashes)
|
||||
# twinkle_speed=300ms # twinkle speed (quick flash)
|
||||
# )
|
||||
# bright_flash.priority = 15
|
||||
#
|
||||
@ -40,15 +40,25 @@ import animation
|
||||
|
||||
# Twinkle Stars - Random sparkling white stars
|
||||
# White sparkles on dark blue background
|
||||
var engine = animation.init_strip(60)
|
||||
#strip length 60
|
||||
# Dark blue background
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var night_sky_ = 0xFF000033
|
||||
var background_ = animation.solid(animation.global('night_sky_', 'night_sky'))
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = animation.global('night_sky_', 'night_sky')
|
||||
# White twinkling stars
|
||||
var stars_ = animation.twinkle_animation(0xFFFFFFFF, 8, 500)
|
||||
var stars_ = animation.twinkle_animation(engine)
|
||||
stars_.color = 0xFFFFFFFF
|
||||
stars_.density = 8
|
||||
stars_.twinkle_speed = 500 # twinkle speed (twinkle duration)
|
||||
animation.global('stars_').priority = 10
|
||||
# Add occasional bright flash
|
||||
var bright_flash_ = animation.twinkle_animation(0xFFFFFFAA, 2, 300)
|
||||
var bright_flash_ = animation.twinkle_animation(engine)
|
||||
bright_flash_.color = 0xFFFFFFAA
|
||||
bright_flash_.density = 2
|
||||
bright_flash_.twinkle_speed = 300 # twinkle speed (quick flash)
|
||||
animation.global('bright_flash_').priority = 15
|
||||
# Start all animations
|
||||
# Start all animations/sequences
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Disco Strobe - Fast colorful strobing
|
||||
# Rapid color changes with strobe effects
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define disco color palette
|
||||
palette disco_colors = [
|
||||
@ -15,35 +15,35 @@ palette disco_colors = [
|
||||
]
|
||||
|
||||
# Fast color cycling base
|
||||
animation disco_base = rich_palette_animation(disco_colors, 1s, linear, 255)
|
||||
animation disco_base = rich_palette_animation(palette=disco_colors, cycle_period=1s, transition_type=LINEAR, brightness=255)
|
||||
|
||||
# Add strobe effect
|
||||
disco_base.opacity = square(0, 255, 100ms, 30) # Fast strobe
|
||||
disco_base.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=30) # Fast strobe
|
||||
|
||||
# Add white flash overlay
|
||||
animation white_flash = solid(0xFFFFFF)
|
||||
white_flash.opacity = square(0, 255, 50ms, 10) # Quick white flashes
|
||||
animation white_flash = solid(color=0xFFFFFF)
|
||||
white_flash.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=10) # Quick white flashes
|
||||
white_flash.priority = 20
|
||||
|
||||
# Add colored sparkles
|
||||
pattern sparkle_pattern = rich_palette_color_provider(disco_colors, 500ms, linear, 255)
|
||||
color sparkle_pattern = rich_palette(palette=disco_colors, cycle_period=500ms, transition_type=LINEAR, brightness=255)
|
||||
animation disco_sparkles = twinkle_animation(
|
||||
sparkle_pattern, # color source
|
||||
12, # density (many sparkles)
|
||||
80ms # twinkle speed (very quick)
|
||||
color=sparkle_pattern, # color source
|
||||
density=12, # density (many sparkles)
|
||||
twinkle_speed=80ms # twinkle speed (very quick)
|
||||
)
|
||||
disco_sparkles.priority = 15
|
||||
|
||||
# Add moving pulse for extra effect
|
||||
pattern pulse_pattern = rich_palette_color_provider(disco_colors, 800ms, linear, 255)
|
||||
animation disco_pulse = pulse_position_animation(
|
||||
pulse_pattern, # color source
|
||||
4, # initial position
|
||||
8, # pulse width
|
||||
2 # sharp edges (slew size)
|
||||
color pulse_pattern = rich_palette(palette=disco_colors, cycle_period=800ms, transition_type=LINEAR, brightness=255)
|
||||
animation disco_pulse = beacon_animation(
|
||||
color=pulse_pattern, # color source
|
||||
pos=4, # initial position
|
||||
beacon_size=8, # pulse width
|
||||
slew_size=2 # sharp edges (slew size)
|
||||
)
|
||||
disco_pulse.priority = 10
|
||||
disco_pulse.pos = sawtooth(4, 56, 2s) # Fast movement
|
||||
disco_pulse.pos = sawtooth(min_value=4, max_value=56, duration=2s) # Fast movement
|
||||
|
||||
# Start all animations
|
||||
run disco_base
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Fire Flicker - Realistic fire simulation
|
||||
# Warm colors with random flickering intensity
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define fire palette from black to yellow
|
||||
palette fire_colors = [
|
||||
@ -13,17 +13,17 @@ palette fire_colors = [
|
||||
]
|
||||
|
||||
# Create base fire animation with palette
|
||||
animation fire_base = rich_palette_animation(fire_colors, 3s, linear, 255)
|
||||
animation fire_base = rich_palette_animation(palette=fire_colors, cycle_period=3s, transition_type=LINEAR, brightness=255)
|
||||
|
||||
# Add flickering effect with random intensity changes
|
||||
fire_base.opacity = smooth(180, 255, 800ms)
|
||||
fire_base.opacity = smooth(min_value=180, max_value=255, duration=800ms)
|
||||
|
||||
# Add subtle position variation for more realism
|
||||
pattern flicker_pattern = rich_palette_color_provider(fire_colors, 2s, linear, 255)
|
||||
color flicker_pattern = rich_palette(palette=fire_colors, cycle_period=2s, transition_type=LINEAR, brightness=255)
|
||||
animation fire_flicker = twinkle_animation(
|
||||
flicker_pattern, # color source
|
||||
12, # density (number of flickers)
|
||||
200ms # twinkle speed (flicker duration)
|
||||
color=flicker_pattern, # color source
|
||||
density=12, # density (number of flickers)
|
||||
twinkle_speed=200ms # twinkle speed (flicker duration)
|
||||
)
|
||||
fire_flicker.priority = 10
|
||||
|
||||
|
||||
@ -1,38 +1,38 @@
|
||||
# Heartbeat Pulse - Rhythmic double pulse
|
||||
# Red pulsing like a heartbeat
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark background
|
||||
color heart_bg = 0x110000
|
||||
animation background = solid(heart_bg)
|
||||
animation background = solid(color=heart_bg)
|
||||
|
||||
# Define heartbeat timing - double pulse pattern
|
||||
# Define heartbeat timing - double pulse animation
|
||||
# First pulse (stronger)
|
||||
animation heartbeat1 = solid(0xFF0000) # Bright red
|
||||
heartbeat1.opacity = square(0, 255, 150ms, 20) # Quick strong pulse
|
||||
animation heartbeat1 = solid(color=0xFF0000) # Bright red
|
||||
heartbeat1.opacity = square(min_value=0, max_value=255, duration=150ms, duty_cycle=20) # Quick strong pulse
|
||||
heartbeat1.priority = 10
|
||||
|
||||
# Second pulse (weaker, slightly delayed)
|
||||
animation heartbeat2 = solid(0xCC0000) # Slightly dimmer red
|
||||
animation heartbeat2 = solid(color=0xCC0000) # Slightly dimmer red
|
||||
# Delay the second pulse by adjusting the square wave phase
|
||||
heartbeat2.opacity = square(0, 180, 150ms, 15) # Weaker pulse
|
||||
heartbeat2.opacity = square(min_value=0, max_value=180, duration=150ms, duty_cycle=15) # Weaker pulse
|
||||
heartbeat2.priority = 8
|
||||
|
||||
# Add subtle glow effect
|
||||
animation heart_glow = solid(0x660000) # Dim red glow
|
||||
heart_glow.opacity = smooth(30, 100, 1s) # Gentle breathing glow
|
||||
animation heart_glow = solid(color=0x660000) # Dim red glow
|
||||
heart_glow.opacity = smooth(min_value=30, max_value=100, duration=1s) # Gentle breathing glow
|
||||
heart_glow.priority = 5
|
||||
|
||||
# Add center pulse for emphasis
|
||||
animation center_pulse = pulse_position_animation(
|
||||
0xFFFFFF, # White center
|
||||
30, # center of strip
|
||||
4, # small center
|
||||
2 # soft edges
|
||||
animation center_pulse = beacon_animation(
|
||||
color=0xFFFFFF, # White center
|
||||
pos=30, # center of strip
|
||||
beacon_size=4, # small center
|
||||
slew_size=2 # soft edges
|
||||
)
|
||||
center_pulse.priority = 20
|
||||
center_pulse.opacity = square(0, 200, 100ms, 10) # Quick white flash
|
||||
center_pulse.opacity = square(min_value=0, max_value=200, duration=100ms, duty_cycle=10) # Quick white flash
|
||||
|
||||
# Start all animations
|
||||
run background
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Lava Lamp - Slow flowing warm colors
|
||||
# Organic movement like a lava lamp
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define lava colors (warm oranges and reds)
|
||||
palette lava_colors = [
|
||||
@ -13,45 +13,45 @@ palette lava_colors = [
|
||||
]
|
||||
|
||||
# Base lava animation - very slow color changes
|
||||
animation lava_base = rich_palette_animation(lava_colors, 15s, smooth, 180)
|
||||
animation lava_base = rich_palette_animation(palette=lava_colors, cycle_period=15s, transition_type=SINE, brightness=180)
|
||||
|
||||
# Add slow-moving lava blobs
|
||||
pattern blob1_pattern = rich_palette_color_provider(lava_colors, 12s, smooth, 255)
|
||||
animation lava_blob1 = pulse_position_animation(
|
||||
blob1_pattern, # color source
|
||||
9, # initial position
|
||||
18, # large blob
|
||||
12 # very soft edges
|
||||
color blob1_pattern = rich_palette(palette=lava_colors, cycle_period=12s, transition_type=SINE, brightness=255)
|
||||
animation lava_blob1 = beacon_animation(
|
||||
color=blob1_pattern, # color source
|
||||
pos=9, # initial position
|
||||
beacon_size=18, # large blob
|
||||
slew_size=12 # very soft edges
|
||||
)
|
||||
lava_blob1.priority = 10
|
||||
lava_blob1.pos = smooth(9, 51, 20s) # Very slow movement
|
||||
lava_blob1.pos = smooth(min_value=9, max_value=51, duration=20s) # Very slow movement
|
||||
|
||||
pattern blob2_pattern = rich_palette_color_provider(lava_colors, 10s, smooth, 220)
|
||||
animation lava_blob2 = pulse_position_animation(
|
||||
blob2_pattern, # color source
|
||||
46, # initial position
|
||||
14, # medium blob
|
||||
10 # soft edges
|
||||
color blob2_pattern = rich_palette(palette=lava_colors, cycle_period=10s, transition_type=SINE, brightness=220)
|
||||
animation lava_blob2 = beacon_animation(
|
||||
color=blob2_pattern, # color source
|
||||
pos=46, # initial position
|
||||
beacon_size=14, # medium blob
|
||||
slew_size=10 # soft edges
|
||||
)
|
||||
lava_blob2.priority = 8
|
||||
lava_blob2.pos = smooth(46, 14, 25s) # Opposite direction, slower
|
||||
lava_blob2.pos = smooth(min_value=46, max_value=14, duration=25s) # Opposite direction, slower
|
||||
|
||||
pattern blob3_pattern = rich_palette_color_provider(lava_colors, 8s, smooth, 200)
|
||||
animation lava_blob3 = pulse_position_animation(
|
||||
blob3_pattern, # color source
|
||||
25, # initial position
|
||||
10, # smaller blob
|
||||
8 # soft edges
|
||||
color blob3_pattern = rich_palette(palette=lava_colors, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
animation lava_blob3 = beacon_animation(
|
||||
color=blob3_pattern, # color source
|
||||
pos=25, # initial position
|
||||
beacon_size=10, # smaller blob
|
||||
slew_size=8 # soft edges
|
||||
)
|
||||
lava_blob3.priority = 6
|
||||
lava_blob3.pos = smooth(25, 35, 18s) # Small movement range
|
||||
lava_blob3.pos = smooth(min_value=25, max_value=35, duration=18s) # Small movement range
|
||||
|
||||
# Add subtle heat shimmer effect
|
||||
pattern shimmer_pattern = rich_palette_color_provider(lava_colors, 6s, smooth, 255)
|
||||
color shimmer_pattern = rich_palette(palette=lava_colors, cycle_period=6s, transition_type=SINE, brightness=255)
|
||||
animation heat_shimmer = twinkle_animation(
|
||||
shimmer_pattern, # color source
|
||||
6, # density (shimmer points)
|
||||
1.5s # twinkle speed (slow shimmer)
|
||||
color=shimmer_pattern, # color source
|
||||
density=6, # density (shimmer points)
|
||||
twinkle_speed=1.5s # twinkle speed (slow shimmer)
|
||||
)
|
||||
heat_shimmer.priority = 15
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Lightning Storm - Random lightning flashes
|
||||
# Dark stormy background with bright lightning
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark stormy background with subtle purple/blue
|
||||
palette storm_colors = [
|
||||
@ -10,33 +10,33 @@ palette storm_colors = [
|
||||
(255, 0x220033) # Slightly lighter purple
|
||||
]
|
||||
|
||||
animation storm_bg = rich_palette_animation(storm_colors, 12s, smooth, 100)
|
||||
animation storm_bg = rich_palette_animation(palette=storm_colors, cycle_period=12s, transition_type=SINE, brightness=100)
|
||||
|
||||
# Random lightning flashes - full strip
|
||||
animation lightning_main = solid(0xFFFFFF) # Bright white
|
||||
lightning_main.opacity = square(0, 255, 80ms, 3) # Quick bright flashes
|
||||
animation lightning_main = solid(color=0xFFFFFF) # Bright white
|
||||
lightning_main.opacity = square(min_value=0, max_value=255, duration=80ms, duty_cycle=3) # Quick bright flashes
|
||||
lightning_main.priority = 20
|
||||
|
||||
# Secondary lightning - partial strip
|
||||
animation lightning_partial = pulse_position_animation(
|
||||
0xFFFFAA, # Slightly yellow white
|
||||
30, # center position
|
||||
20, # covers part of strip
|
||||
5 # soft edges
|
||||
animation lightning_partial = beacon_animation(
|
||||
color=0xFFFFAA, # Slightly yellow white
|
||||
pos=30, # center position
|
||||
beacon_size=20, # covers part of strip
|
||||
slew_size=5 # soft edges
|
||||
)
|
||||
lightning_partial.priority = 15
|
||||
lightning_partial.opacity = square(0, 200, 120ms, 4) # Different timing
|
||||
lightning_partial.opacity = square(min_value=0, max_value=200, duration=120ms, duty_cycle=4) # Different timing
|
||||
|
||||
# Add blue afterglow
|
||||
animation afterglow = solid(0x4444FF) # Blue glow
|
||||
afterglow.opacity = square(0, 80, 200ms, 8) # Longer, dimmer glow
|
||||
animation afterglow = solid(color=0x4444FF) # Blue glow
|
||||
afterglow.opacity = square(min_value=0, max_value=80, duration=200ms, duty_cycle=8) # Longer, dimmer glow
|
||||
afterglow.priority = 10
|
||||
|
||||
# Distant thunder (dim flashes)
|
||||
animation distant_flash = twinkle_animation(
|
||||
0x666699, # Dim blue-white
|
||||
4, # density (few flashes)
|
||||
300ms # twinkle speed (medium duration)
|
||||
color=0x666699, # Dim blue-white
|
||||
density=4, # density (few flashes)
|
||||
twinkle_speed=300ms # twinkle speed (medium duration)
|
||||
)
|
||||
distant_flash.priority = 5
|
||||
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# Matrix Rain - Digital rain effect
|
||||
# Green cascading code like The Matrix
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark background
|
||||
color matrix_bg = 0x000000
|
||||
animation background = solid(matrix_bg)
|
||||
animation background = solid(color=matrix_bg)
|
||||
|
||||
# Define matrix green palette
|
||||
palette matrix_greens = [
|
||||
@ -17,35 +17,35 @@ palette matrix_greens = [
|
||||
]
|
||||
|
||||
# Create multiple cascading streams
|
||||
pattern stream1_pattern = rich_palette_color_provider(matrix_greens, 2s, linear, 255)
|
||||
color stream1_pattern = rich_palette(palette=matrix_greens, cycle_period=2s, transition_type=LINEAR, brightness=255)
|
||||
animation stream1 = comet_animation(
|
||||
stream1_pattern, # color source
|
||||
15, # long tail
|
||||
1.5s # speed
|
||||
color=stream1_pattern, # color source
|
||||
tail_length=15, # long tail
|
||||
speed=1.5s # speed
|
||||
)
|
||||
stream1.priority = 10
|
||||
|
||||
pattern stream2_pattern = rich_palette_color_provider(matrix_greens, 1.8s, linear, 200)
|
||||
color stream2_pattern = rich_palette(palette=matrix_greens, cycle_period=1.8s, transition_type=LINEAR, brightness=200)
|
||||
animation stream2 = comet_animation(
|
||||
stream2_pattern, # color source
|
||||
12, # medium tail
|
||||
2.2s # different speed
|
||||
color=stream2_pattern, # color source
|
||||
tail_length=12, # medium tail
|
||||
speed=2.2s # different speed
|
||||
)
|
||||
stream2.priority = 8
|
||||
|
||||
pattern stream3_pattern = rich_palette_color_provider(matrix_greens, 2.5s, linear, 180)
|
||||
color stream3_pattern = rich_palette(palette=matrix_greens, cycle_period=2.5s, transition_type=LINEAR, brightness=180)
|
||||
animation stream3 = comet_animation(
|
||||
stream3_pattern, # color source
|
||||
10, # shorter tail
|
||||
1.8s # another speed
|
||||
color=stream3_pattern, # color source
|
||||
tail_length=10, # shorter tail
|
||||
speed=1.8s # another speed
|
||||
)
|
||||
stream3.priority = 6
|
||||
|
||||
# Add random bright flashes (like code highlights)
|
||||
animation code_flash = twinkle_animation(
|
||||
0x00FFAA, # Bright cyan-green
|
||||
3, # density (few flashes)
|
||||
150ms # twinkle speed (quick flash)
|
||||
color=0x00FFAA, # Bright cyan-green
|
||||
density=3, # density (few flashes)
|
||||
twinkle_speed=150ms # twinkle speed (quick flash)
|
||||
)
|
||||
code_flash.priority = 20
|
||||
|
||||
|
||||
@ -1,54 +1,54 @@
|
||||
# Meteor Shower - Multiple meteors with trails
|
||||
# Fast moving bright objects with fading trails
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark space background
|
||||
color space_bg = 0x000011
|
||||
animation background = solid(space_bg)
|
||||
animation background = solid(color=space_bg)
|
||||
|
||||
# Multiple meteors with different speeds and colors
|
||||
animation meteor1 = comet_animation(
|
||||
0xFFFFFF, # Bright white
|
||||
12, # long trail
|
||||
1.5s # fast speed
|
||||
color=0xFFFFFF, # Bright white
|
||||
tail_length=12, # long trail
|
||||
speed=1.5s # fast speed
|
||||
)
|
||||
meteor1.priority = 15
|
||||
|
||||
animation meteor2 = comet_animation(
|
||||
0xFFAA00, # Orange
|
||||
10, # medium trail
|
||||
2s # medium speed
|
||||
color=0xFFAA00, # Orange
|
||||
tail_length=10, # medium trail
|
||||
speed=2s # medium speed
|
||||
)
|
||||
meteor2.priority = 12
|
||||
|
||||
animation meteor3 = comet_animation(
|
||||
0xAAAAFF, # Blue-white
|
||||
8, # shorter trail
|
||||
1.8s # fast speed
|
||||
color=0xAAAAFF, # Blue-white
|
||||
tail_length=8, # shorter trail
|
||||
speed=1.8s # fast speed
|
||||
)
|
||||
meteor3.priority = 10
|
||||
|
||||
animation meteor4 = comet_animation(
|
||||
0xFFAAAA, # Pink-white
|
||||
14, # long trail
|
||||
2.5s # slower speed
|
||||
color=0xFFAAAA, # Pink-white
|
||||
tail_length=14, # long trail
|
||||
speed=2.5s # slower speed
|
||||
)
|
||||
meteor4.priority = 8
|
||||
|
||||
# Add distant stars
|
||||
animation stars = twinkle_animation(
|
||||
0xCCCCCC, # Dim white
|
||||
12, # density (many stars)
|
||||
2s # twinkle speed (slow twinkle)
|
||||
color=0xCCCCCC, # Dim white
|
||||
density=12, # density (many stars)
|
||||
twinkle_speed=2s # twinkle speed (slow twinkle)
|
||||
)
|
||||
stars.priority = 5
|
||||
|
||||
# Add occasional bright flash (meteor explosion)
|
||||
animation meteor_flash = twinkle_animation(
|
||||
0xFFFFFF, # Bright white
|
||||
1, # density (single flash)
|
||||
100ms # twinkle speed (very quick)
|
||||
color=0xFFFFFF, # Bright white
|
||||
density=1, # density (single flash)
|
||||
twinkle_speed=100ms # twinkle speed (very quick)
|
||||
)
|
||||
meteor_flash.priority = 25
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Neon Glow - Electric neon tube effect
|
||||
# Bright saturated colors with flickering
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define neon colors
|
||||
palette neon_colors = [
|
||||
@ -12,47 +12,47 @@ palette neon_colors = [
|
||||
]
|
||||
|
||||
# Main neon glow with color cycling
|
||||
animation neon_main = rich_palette_animation(neon_colors, 4s, linear, 255)
|
||||
animation neon_main = rich_palette_animation(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
|
||||
|
||||
# Add electrical flickering
|
||||
neon_main.opacity = smooth(220, 255, 200ms)
|
||||
neon_main.opacity = smooth(min_value=220, max_value=255, duration=200ms)
|
||||
|
||||
# Add occasional electrical surge
|
||||
animation neon_surge = solid(0xFFFFFF) # White surge
|
||||
neon_surge.opacity = square(0, 255, 50ms, 2) # Quick bright surges
|
||||
animation neon_surge = solid(color=0xFFFFFF) # White surge
|
||||
neon_surge.opacity = square(min_value=0, max_value=255, duration=50ms, duty_cycle=2) # Quick bright surges
|
||||
neon_surge.priority = 20
|
||||
|
||||
# Add neon tube segments with gaps
|
||||
pattern segment_pattern = rich_palette_color_provider(neon_colors, 4s, linear, 255)
|
||||
animation segment1 = pulse_position_animation(
|
||||
segment_pattern, # color source
|
||||
6, # position
|
||||
12, # segment length
|
||||
1 # sharp edges
|
||||
color segment_pattern = rich_palette(palette=neon_colors, cycle_period=4s, transition_type=LINEAR, brightness=255)
|
||||
animation segment1 = beacon_animation(
|
||||
color=segment_pattern, # color source
|
||||
pos=6, # position
|
||||
beacon_size=12, # segment length
|
||||
slew_size=1 # sharp edges
|
||||
)
|
||||
segment1.priority = 10
|
||||
|
||||
animation segment2 = pulse_position_animation(
|
||||
segment_pattern, # color source
|
||||
24, # position
|
||||
12, # segment length
|
||||
1 # sharp edges
|
||||
animation segment2 = beacon_animation(
|
||||
color=segment_pattern, # color source
|
||||
pos=24, # position
|
||||
beacon_size=12, # segment length
|
||||
slew_size=1 # sharp edges
|
||||
)
|
||||
segment2.priority = 10
|
||||
|
||||
animation segment3 = pulse_position_animation(
|
||||
segment_pattern, # color source
|
||||
42, # position
|
||||
12, # segment length
|
||||
1 # sharp edges
|
||||
animation segment3 = beacon_animation(
|
||||
color=segment_pattern, # color source
|
||||
pos=42, # position
|
||||
beacon_size=12, # segment length
|
||||
slew_size=1 # sharp edges
|
||||
)
|
||||
segment3.priority = 10
|
||||
|
||||
# Add electrical arcing between segments
|
||||
animation arc_sparkles = twinkle_animation(
|
||||
0xAAAAFF, # Electric blue
|
||||
4, # density (few arcs)
|
||||
100ms # twinkle speed (quick arcs)
|
||||
color=0xAAAAFF, # Electric blue
|
||||
density=4, # density (few arcs)
|
||||
twinkle_speed=100ms # twinkle speed (quick arcs)
|
||||
)
|
||||
arc_sparkles.priority = 15
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Ocean Waves - Blue-green wave simulation
|
||||
# Flowing water colors with wave motion
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define ocean color palette
|
||||
palette ocean_colors = [
|
||||
@ -13,34 +13,34 @@ palette ocean_colors = [
|
||||
]
|
||||
|
||||
# Base ocean animation with slow color cycling
|
||||
animation ocean_base = rich_palette_animation(ocean_colors, 8s, smooth, 200)
|
||||
animation ocean_base = rich_palette_animation(palette=ocean_colors, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
|
||||
# Add wave motion with moving pulses
|
||||
pattern wave1_pattern = rich_palette_color_provider(ocean_colors, 6s, smooth, 255)
|
||||
animation wave1 = pulse_position_animation(
|
||||
wave1_pattern, # color source
|
||||
0, # initial position
|
||||
12, # wave width
|
||||
6 # soft edges
|
||||
color wave1_pattern = rich_palette(palette=ocean_colors, cycle_period=6s, transition_type=SINE, brightness=255)
|
||||
animation wave1 = beacon_animation(
|
||||
color=wave1_pattern, # color source
|
||||
pos=0, # initial position
|
||||
beacon_size=12, # wave width
|
||||
slew_size=6 # soft edges
|
||||
)
|
||||
wave1.priority = 10
|
||||
wave1.pos = sawtooth(0, 48, 5s) # 60-12 = 48
|
||||
wave1.pos = sawtooth(min_value=0, max_value=48, duration=5s) # 60-12 = 48
|
||||
|
||||
pattern wave2_pattern = rich_palette_color_provider(ocean_colors, 4s, smooth, 180)
|
||||
animation wave2 = pulse_position_animation(
|
||||
wave2_pattern, # color source
|
||||
52, # initial position
|
||||
8, # smaller wave
|
||||
4 # soft edges
|
||||
color wave2_pattern = rich_palette(palette=ocean_colors, cycle_period=4s, transition_type=SINE, brightness=180)
|
||||
animation wave2 = beacon_animation(
|
||||
color=wave2_pattern, # color source
|
||||
pos=52, # initial position
|
||||
beacon_size=8, # smaller wave
|
||||
slew_size=4 # soft edges
|
||||
)
|
||||
wave2.priority = 8
|
||||
wave2.pos = sawtooth(52, 8, 7s) # Opposite direction
|
||||
wave2.pos = sawtooth(min_value=52, max_value=8, duration=7s) # Opposite direction
|
||||
|
||||
# Add foam sparkles
|
||||
animation foam = twinkle_animation(
|
||||
0xFFFFFF, # White foam
|
||||
6, # density (sparkle count)
|
||||
300ms # twinkle speed (quick sparkles)
|
||||
color=0xFFFFFF, # White foam
|
||||
density=6, # density (sparkle count)
|
||||
twinkle_speed=300ms # twinkle speed (quick sparkles)
|
||||
)
|
||||
foam.priority = 15
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Palette Demo - Shows how to use custom palettes in DSL
|
||||
# This demonstrates the new palette syntax
|
||||
|
||||
strip length 30
|
||||
#strip length 30
|
||||
|
||||
# Define a fire palette
|
||||
palette fire_colors = [
|
||||
@ -22,9 +22,9 @@ palette ocean_colors = [
|
||||
]
|
||||
|
||||
# Create animations using the palettes
|
||||
animation fire_anim = rich_palette_animation(fire_colors, 5s)
|
||||
animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=5s)
|
||||
|
||||
animation ocean_anim = rich_palette_animation(ocean_colors, 8s)
|
||||
animation ocean_anim = rich_palette_animation(palette=ocean_colors, cycle_period=8s)
|
||||
|
||||
# Sequence to show both palettes
|
||||
sequence palette_demo {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Palette Showcase - Demonstrates all palette features
|
||||
# This example shows the full range of palette capabilities
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Example 1: Fire palette with hex colors
|
||||
palette fire_gradient = [
|
||||
@ -44,13 +44,13 @@ palette sunset_sky = [
|
||||
]
|
||||
|
||||
# Create animations using each palette
|
||||
animation fire_effect = rich_palette_animation(fire_gradient, 3s)
|
||||
animation fire_effect = rich_palette_animation(palette=fire_gradient, cycle_period=3s)
|
||||
|
||||
animation ocean_waves = rich_palette_animation(ocean_depths, 8s, smooth, 200)
|
||||
animation ocean_waves = rich_palette_animation(palette=ocean_depths, cycle_period=8s, transition_type=SINE, brightness=200)
|
||||
|
||||
animation aurora_lights = rich_palette_animation(aurora_borealis, 12s, smooth, 180)
|
||||
animation aurora_lights = rich_palette_animation(palette=aurora_borealis, cycle_period=12s, transition_type=SINE, brightness=180)
|
||||
|
||||
animation sunset_glow = rich_palette_animation(sunset_sky, 6s, smooth, 220)
|
||||
animation sunset_glow = rich_palette_animation(palette=sunset_sky, cycle_period=6s, transition_type=SINE, brightness=220)
|
||||
|
||||
# Sequence to showcase all palettes
|
||||
sequence palette_showcase {
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
# Unified Pattern-Animation Demo
|
||||
# This DSL example demonstrates the new unified architecture where Animation extends Pattern
|
||||
|
||||
strip length 30
|
||||
|
||||
# UNIFIED ARCHITECTURE: solid() returns Animation (which IS a Pattern)
|
||||
# No more artificial distinction between patterns and animations
|
||||
animation solid_red = solid(red) # Animation: solid red (infinite duration)
|
||||
animation solid_blue = solid(blue) # Animation: solid blue (infinite duration)
|
||||
animation solid_green = solid(green) # Animation: solid green (infinite duration)
|
||||
|
||||
# COMPOSITION: Animations can use other animations as base
|
||||
animation pulsing_red = pulse(solid_red, 0%, 100%, 2s) # Animation using animation
|
||||
animation pulsing_blue = pulse(solid_blue, 50%, 100%, 1s) # Animation using animation
|
||||
animation pulsing_green = pulse(solid_green, 20%, 80%, 3s) # Animation using animation
|
||||
|
||||
# Set priorities (all animations inherit from Pattern)
|
||||
solid_red.priority = 0 # Base animation priority
|
||||
pulsing_red.priority = 10 # Higher priority
|
||||
pulsing_blue.priority = 20 # Even higher priority
|
||||
pulsing_green.priority = 5 # Medium priority
|
||||
|
||||
# Set opacity (all animations inherit from Pattern)
|
||||
solid_red.opacity = 255 # Full opacity
|
||||
pulsing_red.opacity = 200 # Slightly dimmed
|
||||
pulsing_blue.opacity = 150 # More dimmed
|
||||
|
||||
# RECURSIVE COMPOSITION: Animations can use other animations!
|
||||
# This creates infinitely composable effects
|
||||
animation complex_pulse = pulse(pulsing_red, 30%, 70%, 4s) # Pulse a pulsing animation!
|
||||
|
||||
# Create a sequence that demonstrates the unified architecture
|
||||
sequence unified_demo {
|
||||
# All animations can be used directly in sequences
|
||||
play solid_red for 2s # Use solid animation
|
||||
wait 500ms
|
||||
|
||||
# Composed animations work seamlessly
|
||||
play pulsing_red for 3s # Use pulse animation
|
||||
wait 500ms
|
||||
|
||||
play pulsing_blue for 2s # Use another pulse animation
|
||||
wait 500ms
|
||||
|
||||
# Show recursive composition - animation using animation
|
||||
play complex_pulse for 4s # Nested animation effect
|
||||
wait 500ms
|
||||
|
||||
# Show that all animations support the same properties
|
||||
repeat 2 times:
|
||||
play solid_green for 1s # Animation with priority/opacity
|
||||
play pulsing_green for 2s # Animation with priority/opacity
|
||||
wait 500ms
|
||||
}
|
||||
|
||||
# Run the demonstration
|
||||
run unified_demo
|
||||
@ -1,7 +1,7 @@
|
||||
# Plasma Wave - Smooth flowing plasma colors
|
||||
# Continuous color waves like plasma display
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define plasma color palette with smooth transitions
|
||||
palette plasma_colors = [
|
||||
@ -14,41 +14,41 @@ palette plasma_colors = [
|
||||
]
|
||||
|
||||
# Base plasma animation with medium speed
|
||||
animation plasma_base = rich_palette_animation(plasma_colors, 6s, smooth, 200)
|
||||
animation plasma_base = rich_palette_animation(palette=plasma_colors, cycle_period=6s, transition_type=SINE, brightness=200)
|
||||
|
||||
# Add multiple wave layers for complexity
|
||||
pattern wave1_pattern = rich_palette_color_provider(plasma_colors, 4s, smooth, 255)
|
||||
animation plasma_wave1 = pulse_position_animation(
|
||||
wave1_pattern, # color source
|
||||
0, # initial position
|
||||
20, # wide wave
|
||||
10 # very smooth
|
||||
color wave1_pattern = rich_palette(palette=plasma_colors, cycle_period=4s, transition_type=SINE, brightness=255)
|
||||
animation plasma_wave1 = beacon_animation(
|
||||
color=wave1_pattern, # color source
|
||||
pos=0, # initial position
|
||||
beacon_size=20, # wide wave
|
||||
slew_size=10 # very smooth
|
||||
)
|
||||
plasma_wave1.priority = 10
|
||||
plasma_wave1.pos = smooth(0, 40, 8s)
|
||||
plasma_wave1.pos = smooth(min_value=0, max_value=40, duration=8s)
|
||||
|
||||
pattern wave2_pattern = rich_palette_color_provider(plasma_colors, 5s, smooth, 180)
|
||||
animation plasma_wave2 = pulse_position_animation(
|
||||
wave2_pattern, # color source
|
||||
45, # initial position
|
||||
15, # medium wave
|
||||
8 # smooth
|
||||
color wave2_pattern = rich_palette(palette=plasma_colors, cycle_period=5s, transition_type=SINE, brightness=180)
|
||||
animation plasma_wave2 = beacon_animation(
|
||||
color=wave2_pattern, # color source
|
||||
pos=45, # initial position
|
||||
beacon_size=15, # medium wave
|
||||
slew_size=8 # smooth
|
||||
)
|
||||
plasma_wave2.priority = 8
|
||||
plasma_wave2.pos = smooth(45, 15, 10s) # Opposite direction
|
||||
plasma_wave2.pos = smooth(min_value=45, max_value=15, duration=10s) # Opposite direction
|
||||
|
||||
pattern wave3_pattern = rich_palette_color_provider(plasma_colors, 3s, smooth, 220)
|
||||
animation plasma_wave3 = pulse_position_animation(
|
||||
wave3_pattern, # color source
|
||||
20, # initial position
|
||||
12, # smaller wave
|
||||
6 # smooth
|
||||
color wave3_pattern = rich_palette(palette=plasma_colors, cycle_period=3s, transition_type=SINE, brightness=220)
|
||||
animation plasma_wave3 = beacon_animation(
|
||||
color=wave3_pattern, # color source
|
||||
pos=20, # initial position
|
||||
beacon_size=12, # smaller wave
|
||||
slew_size=6 # smooth
|
||||
)
|
||||
plasma_wave3.priority = 12
|
||||
plasma_wave3.pos = smooth(20, 50, 6s) # Different speed
|
||||
plasma_wave3.pos = smooth(min_value=20, max_value=50, duration=6s) # Different speed
|
||||
|
||||
# Add subtle intensity variation
|
||||
plasma_base.opacity = smooth(150, 255, 12s)
|
||||
plasma_base.opacity = smooth(min_value=150, max_value=255, duration=12s)
|
||||
|
||||
# Start all animations
|
||||
run plasma_base
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
# Police Lights - Red and blue alternating flashes
|
||||
# Emergency vehicle style lighting
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define zones for left and right halves
|
||||
set half_length = 30
|
||||
|
||||
# Left side red flashing
|
||||
animation left_red = pulse_position_animation(
|
||||
0xFF0000, # Bright red
|
||||
15, # center of left half
|
||||
15, # half the strip
|
||||
2 # sharp edges
|
||||
animation left_red = beacon_animation(
|
||||
color=0xFF0000, # Bright red
|
||||
pos=15, # center of left half
|
||||
beacon_size=15, # half the strip
|
||||
slew_size=2 # sharp edges
|
||||
)
|
||||
left_red.priority = 10
|
||||
left_red.opacity = square(0, 255, 400ms, 50) # 50% duty cycle
|
||||
left_red.opacity = square(min_value=0, max_value=255, duration=400ms, duty_cycle=50) # 50% duty cycle
|
||||
|
||||
# Right side blue flashing (opposite phase)
|
||||
animation right_blue = pulse_position_animation(
|
||||
0x0000FF, # Bright blue
|
||||
45, # center of right half
|
||||
15, # half the strip
|
||||
2 # sharp edges
|
||||
animation right_blue = beacon_animation(
|
||||
color=0x0000FF, # Bright blue
|
||||
pos=45, # center of right half
|
||||
beacon_size=15, # half the strip
|
||||
slew_size=2 # sharp edges
|
||||
)
|
||||
right_blue.priority = 10
|
||||
right_blue.opacity = square(255, 0, 400ms, 50) # Opposite phase
|
||||
right_blue.opacity = square(min_value=255, max_value=0, duration=400ms, duty_cycle=50) # Opposite phase
|
||||
|
||||
# Add white strobe overlay occasionally
|
||||
animation white_strobe = solid(0xFFFFFF)
|
||||
white_strobe.opacity = square(0, 255, 100ms, 5) # Quick bright flashes
|
||||
animation white_strobe = solid(color=0xFFFFFF)
|
||||
white_strobe.opacity = square(min_value=0, max_value=255, duration=100ms, duty_cycle=5) # Quick bright flashes
|
||||
white_strobe.priority = 20
|
||||
|
||||
# Start all animations
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Property Assignment Demo
|
||||
# Shows how to set animation properties after creation
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define colors
|
||||
color red_custom = 0xFF0000
|
||||
@ -9,12 +9,12 @@ color blue_custom = 0x0000FF
|
||||
color green_custom = 0x00FF00
|
||||
|
||||
# Create animations
|
||||
animation left_pulse = pulse_position_animation(red_custom, 15, 15, 3)
|
||||
animation center_pulse = pulse_position_animation(blue_custom, 30, 15, 3)
|
||||
animation right_pulse = pulse_position_animation(green_custom, 45, 15, 3)
|
||||
animation left_pulse = beacon_animation(color=red_custom, pos=15, beacon_size=15, slew_size=3)
|
||||
animation center_pulse = beacon_animation(color=blue_custom, pos=30, beacon_size=15, slew_size=3)
|
||||
animation right_pulse = beacon_animation(color=green_custom, pos=45, beacon_size=15, slew_size=3)
|
||||
|
||||
# Set different opacities
|
||||
left_pulse.opacity = 255 # Full brightness
|
||||
left_pulse.opacity = 255 # Full slew_size
|
||||
center_pulse.opacity = 200 # Slightly dimmed
|
||||
right_pulse.opacity = 150 # More dimmed
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
# Rainbow Cycle - Classic WLED effect
|
||||
# Smooth rainbow colors cycling across the strip
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Create smooth rainbow cycle animation
|
||||
animation rainbow_cycle = color_cycle_animation(
|
||||
[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
|
||||
5s # cycle period
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF], # rainbow colors
|
||||
cycle_period=5s # cycle period
|
||||
)
|
||||
animation rainbow_animation = solid(color=rainbow_cycle)
|
||||
|
||||
# Start the animation
|
||||
run rainbow_cycle
|
||||
run rainbow_animation
|
||||
@ -1,33 +1,34 @@
|
||||
# Scanner (Larson) - Knight Rider style scanner
|
||||
# Red dot bouncing back and forth
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark background
|
||||
color scanner_bg = 0x110000
|
||||
animation background = solid(scanner_bg)
|
||||
animation background = solid(color=scanner_bg)
|
||||
|
||||
# Main scanner pulse that bounces
|
||||
animation scanner = pulse_position_animation(
|
||||
0xFF0000, # Bright red
|
||||
2, # initial position
|
||||
3, # pulse width
|
||||
2 # fade region
|
||||
animation scanner = beacon_animation(
|
||||
color=0xFF0000, # Bright red
|
||||
pos=2, # initial position
|
||||
beacon_size=3, # pulse width
|
||||
slew_size=2 # fade region
|
||||
)
|
||||
scanner.priority = 10
|
||||
|
||||
# Bouncing position from left to right and back
|
||||
scanner.pos = triangle(2, 57, 2s)
|
||||
scanner.pos = triangle(min_value=2, max_value=57, duration=2s)
|
||||
|
||||
# Add trailing glow effect
|
||||
animation scanner_trail = pulse_position_animation(
|
||||
0x660000, # Dim red trail
|
||||
2, # initial position
|
||||
6, # wider trail
|
||||
4 # more fade
|
||||
animation scanner_trail = beacon_animation(
|
||||
color=0x660000, # Dim red trail
|
||||
pos=2, # initial position
|
||||
beacon_size=6, # wider trail
|
||||
slew_size=4 # more fade
|
||||
)
|
||||
scanner_trail.priority = 5
|
||||
scanner_trail.pos = triangle(2, 57, 2s)
|
||||
set pos_test = triangle(min_value=2, max_value=57, duration=2s)
|
||||
scanner_trail.pos = pos_test
|
||||
scanner_trail.opacity = 128 # Half brightness
|
||||
|
||||
# Start all animations
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Simple Palette Example
|
||||
# Demonstrates basic palette usage in the DSL
|
||||
|
||||
strip length 20
|
||||
#strip length 20
|
||||
|
||||
# Define a simple rainbow palette
|
||||
palette rainbow = [
|
||||
@ -13,7 +13,7 @@ palette rainbow = [
|
||||
]
|
||||
|
||||
# Create an animation using the palette
|
||||
animation rainbow_cycle = rich_palette_animation(rainbow, 3s)
|
||||
animation rainbow_cycle = rich_palette_animation(palette=rainbow, cycle_period=3s)
|
||||
|
||||
# Simple sequence
|
||||
sequence demo {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Sunrise Sunset - Warm color transition
|
||||
# Gradual transition from night to day colors
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Define time-of-day color palette
|
||||
palette daylight_colors = [
|
||||
@ -17,38 +17,38 @@ palette daylight_colors = [
|
||||
]
|
||||
|
||||
# Main daylight cycle - very slow transition
|
||||
animation daylight_cycle = rich_palette_animation(daylight_colors, 60s)
|
||||
animation daylight_cycle = rich_palette_animation(palette=daylight_colors, cycle_period=60s)
|
||||
|
||||
# Add sun position effect - bright spot that moves
|
||||
animation sun_position = pulse_position_animation(
|
||||
0xFFFFAA, # Bright yellow sun
|
||||
5, # initial position
|
||||
8, # sun size
|
||||
4 # soft glow
|
||||
animation sun_position = beacon_animation(
|
||||
color=0xFFFFAA, # Bright yellow sun
|
||||
pos=5, # initial position
|
||||
beacon_size=8, # sun size
|
||||
slew_size=4 # soft glow
|
||||
)
|
||||
sun_position.priority = 10
|
||||
sun_position.pos = smooth(5, 55, 30s) # Sun arc across sky
|
||||
sun_position.opacity = smooth(0, 255, 30s) # Fade in and out
|
||||
sun_position.pos = smooth(min_value=5, max_value=55, duration=30s) # Sun arc across sky
|
||||
sun_position.opacity = smooth(min_value=0, max_value=255, duration=30s) # Fade in and out
|
||||
|
||||
# Add atmospheric glow around sun
|
||||
animation sun_glow = pulse_position_animation(
|
||||
0xFFCC88, # Warm glow
|
||||
5, # initial position
|
||||
16, # larger glow
|
||||
8 # very soft
|
||||
animation sun_glow = beacon_animation(
|
||||
color=0xFFCC88, # Warm glow
|
||||
pos=5, # initial position
|
||||
beacon_size=16, # larger glow
|
||||
slew_size=8 # very soft
|
||||
)
|
||||
sun_glow.priority = 5
|
||||
sun_glow.pos = smooth(5, 55, 30s) # Follow sun
|
||||
sun_glow.opacity = smooth(0, 150, 30s) # Dimmer glow
|
||||
sun_glow.pos = smooth(min_value=5, max_value=55, duration=30s) # Follow sun
|
||||
sun_glow.opacity = smooth(min_value=0, max_value=150, duration=30s) # Dimmer glow
|
||||
|
||||
# Add twinkling stars during night phases
|
||||
animation stars = twinkle_animation(
|
||||
0xFFFFFF, # White stars
|
||||
6, # density (star count)
|
||||
1s # twinkle speed (slow twinkle)
|
||||
color=0xFFFFFF, # White stars
|
||||
density=6, # density (star count)
|
||||
twinkle_speed=1s # twinkle speed (slow twinkle)
|
||||
)
|
||||
stars.priority = 15
|
||||
stars.opacity = smooth(255, 0, 30s) # Fade out during day
|
||||
stars.opacity = smooth(min_value=255, max_value=0, duration=30s) # Fade out during day
|
||||
|
||||
# Start all animations
|
||||
run daylight_cycle
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
# Twinkle Stars - Random sparkling white stars
|
||||
# White sparkles on dark blue background
|
||||
|
||||
strip length 60
|
||||
#strip length 60
|
||||
|
||||
# Dark blue background
|
||||
color night_sky = 0x000033
|
||||
animation background = solid(night_sky)
|
||||
animation background = solid(color=night_sky)
|
||||
|
||||
# White twinkling stars
|
||||
animation stars = twinkle_animation(
|
||||
0xFFFFFF, # White stars
|
||||
8, # density (number of stars)
|
||||
500ms # twinkle speed (twinkle duration)
|
||||
color=0xFFFFFF, # White stars
|
||||
density=8, # density (number of stars)
|
||||
twinkle_speed=500ms # twinkle speed (twinkle duration)
|
||||
)
|
||||
stars.priority = 10
|
||||
|
||||
# Add occasional bright flash
|
||||
animation bright_flash = twinkle_animation(
|
||||
0xFFFFAA, # Bright yellow-white
|
||||
2, # density (fewer bright flashes)
|
||||
300ms # twinkle speed (quick flash)
|
||||
color=0xFFFFAA, # Bright yellow-white
|
||||
density=2, # density (fewer bright flashes)
|
||||
twinkle_speed=300ms # twinkle speed (quick flash)
|
||||
)
|
||||
bright_flash.priority = 15
|
||||
|
||||
|
||||
1267
lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md
Normal file
1267
lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md
Normal file
File diff suppressed because it is too large
Load Diff
586
lib/libesp32/berry_animation/docs/ANIMATION_DEVELOPMENT.md
Normal file
586
lib/libesp32/berry_animation/docs/ANIMATION_DEVELOPMENT.md
Normal file
@ -0,0 +1,586 @@
|
||||
# Animation Development Guide
|
||||
|
||||
Guide for developers creating custom animation classes in the Berry Animation Framework.
|
||||
|
||||
## Overview
|
||||
|
||||
**Note**: This guide is for developers who want to extend the framework by creating new animation classes. For using existing animations, see the [DSL Reference](DSL_REFERENCE.md) which provides a declarative way to create animations without programming.
|
||||
|
||||
The Berry Animation Framework uses a unified architecture where all visual elements inherit from the base `Animation` class. This guide explains how to create custom animation classes that integrate seamlessly with the framework's parameter system, value providers, and rendering pipeline.
|
||||
|
||||
## Animation Class Structure
|
||||
|
||||
### Basic Class Template
|
||||
|
||||
```berry
|
||||
#@ solidify:MyAnimation,weak
|
||||
class MyAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Parameter definitions following the new specification
|
||||
static var PARAMS = {
|
||||
"my_param1": {"default": "default_value", "type": "string"},
|
||||
"my_param2": {"min": 0, "max": 255, "default": 100, "type": "int"}
|
||||
# Do NOT include inherited Animation parameters here
|
||||
}
|
||||
|
||||
def init(engine)
|
||||
# Engine parameter is MANDATORY and cannot be nil
|
||||
super(self).init(engine)
|
||||
|
||||
# Only initialize non-parameter instance variables (none in this example)
|
||||
# Parameters are handled by the virtual parameter system
|
||||
end
|
||||
|
||||
# Handle parameter changes (optional)
|
||||
def on_param_changed(name, value)
|
||||
# Add custom logic for parameter changes if needed
|
||||
# Parameter validation is handled automatically by the framework
|
||||
end
|
||||
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var param1 = self.my_param1
|
||||
var param2 = self.my_param2
|
||||
|
||||
# Your rendering logic here
|
||||
# ...
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# NO setter methods needed - use direct virtual parameter assignment:
|
||||
# obj.my_param1 = value
|
||||
# obj.my_param2 = value
|
||||
|
||||
def tostring()
|
||||
return f"MyAnimation(param1={self.my_param1}, param2={self.my_param2}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## PARAMS System
|
||||
|
||||
### Static Parameter Definition
|
||||
|
||||
The `PARAMS` static variable defines all parameters specific to your animation class. This system provides:
|
||||
|
||||
- **Parameter validation** with min/max constraints and type checking
|
||||
- **Default value handling** for initialization
|
||||
- **Virtual parameter access** through getmember/setmember
|
||||
- **Automatic ValueProvider resolution**
|
||||
|
||||
#### Parameter Definition Format
|
||||
|
||||
```berry
|
||||
static var PARAMS = {
|
||||
"parameter_name": {
|
||||
"default": default_value, # Default value (optional)
|
||||
"min": minimum_value, # Minimum value for integers (optional)
|
||||
"max": maximum_value, # Maximum value for integers (optional)
|
||||
"enum": [val1, val2, val3], # Valid enum values (optional)
|
||||
"type": "parameter_type", # Expected type (optional)
|
||||
"nillable": true # Whether nil values are allowed (optional)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Supported Types
|
||||
|
||||
- **`"int"`** - Integer values (default if not specified)
|
||||
- **`"string"`** - String values
|
||||
- **`"bool"`** - Boolean values (true/false)
|
||||
- **`"instance"`** - Object instances
|
||||
- **`"any"`** - Any type (no type validation)
|
||||
|
||||
#### Important Rules
|
||||
|
||||
- **Do NOT include inherited parameters** - Animation base class parameters are handled automatically
|
||||
- **Only define class-specific parameters** in your PARAMS
|
||||
- **No constructor parameter mapping** - the new system uses engine-only constructors
|
||||
- **Parameters are accessed via virtual members**: `obj.param_name`
|
||||
|
||||
## Constructor Implementation
|
||||
|
||||
### Engine-Only Constructor Pattern
|
||||
|
||||
```berry
|
||||
def init(engine)
|
||||
# 1. ALWAYS call super with engine (engine is the ONLY parameter)
|
||||
super(self).init(engine)
|
||||
|
||||
# 2. Initialize non-parameter instance variables only
|
||||
self.internal_state = initial_value
|
||||
self.buffer = nil
|
||||
# Do NOT initialize parameters here - they are handled by the virtual system
|
||||
end
|
||||
```
|
||||
|
||||
### Parameter Change Handling
|
||||
|
||||
```berry
|
||||
def on_param_changed(name, value)
|
||||
# Optional method to handle parameter changes
|
||||
if name == "scale"
|
||||
# Recalculate internal state when scale changes
|
||||
self._update_internal_buffers()
|
||||
elif name == "color"
|
||||
# Handle color changes
|
||||
self._invalidate_color_cache()
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Key Changes from Old System
|
||||
|
||||
- **Engine-only constructor**: Constructor takes ONLY the engine parameter
|
||||
- **No parameter initialization**: Parameters are set by caller using virtual member assignment
|
||||
- **No instance variables for parameters**: Parameters are handled by the virtual system
|
||||
- **Automatic validation**: Parameter validation happens automatically based on PARAMS constraints
|
||||
|
||||
## Value Provider Integration
|
||||
|
||||
### Automatic ValueProvider Resolution
|
||||
|
||||
The virtual parameter system automatically resolves ValueProviders when you access parameters:
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
# Virtual parameter access automatically resolves ValueProviders
|
||||
var color = self.color # Returns current color value, not the provider
|
||||
var position = self.pos # Returns current position value
|
||||
var size = self.size # Returns current size value
|
||||
|
||||
# Use resolved values in rendering logic
|
||||
for i: position..(position + size - 1)
|
||||
if i >= 0 && i < frame.width
|
||||
frame.set_pixel_color(i, color)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
```
|
||||
|
||||
### Setting Dynamic Parameters
|
||||
|
||||
Users can set both static values and ValueProviders using the same syntax:
|
||||
|
||||
```berry
|
||||
# Create animation
|
||||
var anim = animation.my_animation(engine)
|
||||
|
||||
# Static values
|
||||
anim.color = 0xFFFF0000
|
||||
anim.pos = 5
|
||||
anim.size = 3
|
||||
|
||||
# Dynamic values
|
||||
anim.color = animation.smooth(0xFF000000, 0xFFFFFFFF, 2000)
|
||||
anim.pos = animation.triangle(0, 29, 3000)
|
||||
```
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
For performance-critical code, cache parameter values:
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
# Cache parameter values to avoid multiple virtual member access
|
||||
var current_color = self.color
|
||||
var current_pos = self.pos
|
||||
var current_size = self.size
|
||||
|
||||
# Use cached values in loops
|
||||
for i: current_pos..(current_pos + current_size - 1)
|
||||
if i >= 0 && i < frame.width
|
||||
frame.set_pixel_color(i, current_color)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
```
|
||||
|
||||
## Parameter Access
|
||||
|
||||
### Direct Virtual Member Assignment
|
||||
|
||||
The new system uses direct parameter assignment instead of setter methods:
|
||||
|
||||
```berry
|
||||
# Create animation
|
||||
var anim = animation.my_animation(engine)
|
||||
|
||||
# Direct parameter assignment (recommended)
|
||||
anim.color = 0xFF00FF00
|
||||
anim.pos = 10
|
||||
anim.size = 5
|
||||
|
||||
# Method chaining is not needed - just set parameters directly
|
||||
```
|
||||
|
||||
### Parameter Validation
|
||||
|
||||
The parameter system handles validation automatically based on PARAMS constraints:
|
||||
|
||||
```berry
|
||||
# This will raise an exception due to min: 0 constraint
|
||||
anim.size = -1 # Raises value_error
|
||||
|
||||
# This will be accepted
|
||||
anim.size = 5 # Parameter updated successfully
|
||||
|
||||
# Method-based setting returns true/false for validation
|
||||
var success = anim.set_param("size", -1) # Returns false, no exception
|
||||
```
|
||||
|
||||
### Accessing Raw Parameters
|
||||
|
||||
```berry
|
||||
# Get current parameter value (resolved if ValueProvider)
|
||||
var current_color = anim.color
|
||||
|
||||
# Get raw parameter (returns ValueProvider if set)
|
||||
var raw_color = anim.get_param("color")
|
||||
|
||||
# Check if parameter is a ValueProvider
|
||||
if animation.is_value_provider(raw_color)
|
||||
print("Color is dynamic")
|
||||
else
|
||||
print("Color is static")
|
||||
end
|
||||
```
|
||||
|
||||
## Rendering Implementation
|
||||
|
||||
### Frame Buffer Operations
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Get frame dimensions
|
||||
var width = frame.width
|
||||
var height = frame.height # Usually 1 for LED strips
|
||||
|
||||
# Resolve dynamic parameters
|
||||
var color = self.resolve_value(self.color, "color", time_ms)
|
||||
var opacity = self.resolve_value(self.opacity, "opacity", time_ms)
|
||||
|
||||
# Render your effect
|
||||
for i: 0..(width-1)
|
||||
var pixel_color = calculate_pixel_color(i, time_ms)
|
||||
frame.set_pixel_color(i, pixel_color)
|
||||
end
|
||||
|
||||
# Apply opacity if not full
|
||||
if opacity < 255
|
||||
frame.apply_opacity(opacity)
|
||||
end
|
||||
|
||||
return true # Frame was modified
|
||||
end
|
||||
```
|
||||
|
||||
### Common Rendering Patterns
|
||||
|
||||
#### Fill Pattern
|
||||
```berry
|
||||
# Fill entire frame with color
|
||||
frame.fill_pixels(color)
|
||||
```
|
||||
|
||||
#### Position-Based Effects
|
||||
```berry
|
||||
# Render at specific positions
|
||||
var start_pos = self.resolve_value(self.pos, "pos", time_ms)
|
||||
var size = self.resolve_value(self.size, "size", time_ms)
|
||||
|
||||
for i: 0..(size-1)
|
||||
var pixel_pos = start_pos + i
|
||||
if pixel_pos >= 0 && pixel_pos < frame.width
|
||||
frame.set_pixel_color(pixel_pos, color)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Gradient Effects
|
||||
```berry
|
||||
# Create gradient across frame
|
||||
for i: 0..(frame.width-1)
|
||||
var progress = i / (frame.width - 1.0) # 0.0 to 1.0
|
||||
var interpolated_color = interpolate_color(start_color, end_color, progress)
|
||||
frame.set_pixel_color(i, interpolated_color)
|
||||
end
|
||||
```
|
||||
|
||||
## Complete Example: PulsePositionAnimation
|
||||
|
||||
Here's a complete example showing all concepts:
|
||||
|
||||
```berry
|
||||
#@ solidify:PulsePositionAnimation,weak
|
||||
class BeaconAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Parameter definitions following the new specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"pos": {"default": 0},
|
||||
"beacon_size": {"min": 0, "default": 1},
|
||||
"slew_size": {"min": 0, "default": 0}
|
||||
}
|
||||
|
||||
# Initialize a new Pulse Position animation
|
||||
# Engine parameter is MANDATORY and cannot be nil
|
||||
def init(engine)
|
||||
# Call parent constructor with engine (engine is the ONLY parameter)
|
||||
super(self).init(engine)
|
||||
|
||||
# Only initialize non-parameter instance variables (none in this case)
|
||||
# Parameters are handled by the virtual parameter system
|
||||
end
|
||||
|
||||
# Handle parameter changes (optional - can be removed if no special handling needed)
|
||||
def on_param_changed(name, value)
|
||||
# No special handling needed for this animation
|
||||
# Parameter validation is handled automatically by the framework
|
||||
end
|
||||
|
||||
# Render the pulse to the provided frame buffer
|
||||
def render(frame, time_ms)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
var pixel_size = frame.width
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
var slew_size = self.slew_size
|
||||
var beacon_size = self.beacon_size
|
||||
var color = self.color
|
||||
|
||||
# Fill background if not transparent
|
||||
if back_color != 0xFF000000
|
||||
frame.fill_pixels(back_color)
|
||||
end
|
||||
|
||||
# Calculate pulse boundaries
|
||||
var pulse_min = pos
|
||||
var pulse_max = pos + beacon_size
|
||||
|
||||
# Clamp to frame boundaries
|
||||
if pulse_min < 0
|
||||
pulse_min = 0
|
||||
end
|
||||
if pulse_max >= pixel_size
|
||||
pulse_max = pixel_size
|
||||
end
|
||||
|
||||
# Draw the main pulse
|
||||
var i = pulse_min
|
||||
while i < pulse_max
|
||||
frame.set_pixel_color(i, color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Draw slew regions if slew_size > 0
|
||||
if slew_size > 0
|
||||
# Left slew (fade from background to pulse color)
|
||||
var left_slew_min = pos - slew_size
|
||||
var left_slew_max = pos
|
||||
|
||||
if left_slew_min < 0
|
||||
left_slew_min = 0
|
||||
end
|
||||
if left_slew_max >= pixel_size
|
||||
left_slew_max = pixel_size
|
||||
end
|
||||
|
||||
i = left_slew_min
|
||||
while i < left_slew_max
|
||||
# Calculate blend factor
|
||||
var blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0)
|
||||
var alpha = 255 - blend_factor
|
||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
||||
var blended_color = frame.blend(back_color, blend_color)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Right slew (fade from pulse color to background)
|
||||
var right_slew_min = pos + beacon_size
|
||||
var right_slew_max = pos + beacon_size + slew_size
|
||||
|
||||
if right_slew_min < 0
|
||||
right_slew_min = 0
|
||||
end
|
||||
if right_slew_max >= pixel_size
|
||||
right_slew_max = pixel_size
|
||||
end
|
||||
|
||||
i = right_slew_min
|
||||
while i < right_slew_max
|
||||
# Calculate blend factor
|
||||
var blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255)
|
||||
var alpha = 255 - blend_factor
|
||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
||||
var blended_color = frame.blend(back_color, blend_color)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# NO setter methods - use direct virtual parameter assignment instead:
|
||||
# obj.color = value
|
||||
# obj.pos = value
|
||||
# obj.beacon_size = value
|
||||
# obj.slew_size = value
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"PulsePositionAnimation(color=0x{self.color :08x}, pos={self.pos}, beacon_size={self.beacon_size}, slew_size={self.slew_size})"
|
||||
end
|
||||
end
|
||||
|
||||
# Export class directly - no redundant factory function needed
|
||||
return {'beacon_animation': PulsePositionAnimation}
|
||||
```
|
||||
|
||||
## Testing Your Animation
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Create comprehensive tests for your animation:
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
def test_my_animation()
|
||||
# Create LED strip and engine for testing
|
||||
var strip = global.Leds(10) # Use built-in LED strip for testing
|
||||
var engine = animation.animation_engine(strip)
|
||||
|
||||
# Test basic construction
|
||||
var anim = animation.my_animation(engine)
|
||||
assert(anim != nil, "Animation should be created")
|
||||
|
||||
# Test parameter setting
|
||||
anim.color = 0xFFFF0000
|
||||
assert(anim.color == 0xFFFF0000, "Color should be set")
|
||||
|
||||
# Test parameter updates
|
||||
anim.color = 0xFF00FF00
|
||||
assert(anim.color == 0xFF00FF00, "Color should be updated")
|
||||
|
||||
# Test value providers
|
||||
var dynamic_color = animation.smooth(engine)
|
||||
dynamic_color.min_value = 0xFF000000
|
||||
dynamic_color.max_value = 0xFFFFFFFF
|
||||
dynamic_color.duration = 2000
|
||||
|
||||
anim.color = dynamic_color
|
||||
var raw_color = anim.get_param("color")
|
||||
assert(animation.is_value_provider(raw_color), "Should accept value provider")
|
||||
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(10)
|
||||
anim.start()
|
||||
var result = anim.render(frame, 1000)
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
print("✓ All tests passed")
|
||||
end
|
||||
|
||||
test_my_animation()
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
Test with the animation engine:
|
||||
|
||||
```berry
|
||||
var strip = global.Leds(30) # Use built-in LED strip
|
||||
var engine = animation.animation_engine(strip)
|
||||
var anim = animation.my_animation(engine)
|
||||
|
||||
# Set parameters
|
||||
anim.color = 0xFFFF0000
|
||||
anim.pos = 5
|
||||
anim.beacon_size = 3
|
||||
|
||||
engine.add_animation(anim)
|
||||
engine.start()
|
||||
|
||||
# Let it run for a few seconds
|
||||
tasmota.delay(3000)
|
||||
|
||||
engine.stop()
|
||||
print("Integration test completed")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance
|
||||
- **Minimize calculations** in render() method
|
||||
- **Cache resolved values** when possible
|
||||
- **Use integer math** instead of floating point
|
||||
- **Avoid memory allocation** in render loops
|
||||
|
||||
### Memory Management
|
||||
- **Reuse objects** when possible
|
||||
- **Clear references** to large objects when done
|
||||
- **Use static variables** for constants
|
||||
|
||||
### Code Organization
|
||||
- **Group related parameters** together
|
||||
- **Use descriptive variable names**
|
||||
- **Comment complex algorithms**
|
||||
- **Follow Berry naming conventions**
|
||||
|
||||
### Error Handling
|
||||
- **Validate parameters** in constructor
|
||||
- **Handle edge cases** gracefully
|
||||
- **Return false** from render() on errors
|
||||
- **Use meaningful error messages**
|
||||
|
||||
## Publishing Your Animation Class
|
||||
|
||||
Once you've created a new animation class:
|
||||
|
||||
1. **Add it to the animation module** by importing it in `animation.be`
|
||||
2. **Create a factory function** following the engine-first pattern
|
||||
3. **Add DSL support** by ensuring the transpiler recognizes your factory function
|
||||
4. **Document parameters** in the class hierarchy documentation
|
||||
5. **Test with DSL** to ensure users can access your animation declaratively
|
||||
|
||||
**Remember**: Users should primarily interact with animations through the DSL. The programmatic API is mainly for framework development and advanced integrations.
|
||||
|
||||
This guide provides everything needed to create professional-quality animation classes that integrate seamlessly with the Berry Animation Framework's parameter system and rendering pipeline.
|
||||
@ -1,750 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
Complete reference for the Tasmota Berry Animation Framework API.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### AnimationEngine
|
||||
|
||||
The central controller for all animations.
|
||||
|
||||
```berry
|
||||
var engine = animation.create_engine(strip)
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
**`add_animation(animation)`**
|
||||
- Adds an animation to the engine
|
||||
- Auto-starts the animation if engine is running
|
||||
- Returns: `self` (for method chaining)
|
||||
|
||||
**`remove_animation(animation)`**
|
||||
- Removes an animation from the engine
|
||||
- Returns: `self`
|
||||
|
||||
**`clear()`**
|
||||
- Removes all animations
|
||||
- Returns: `self`
|
||||
|
||||
**`start()`**
|
||||
- Starts the engine and all animations
|
||||
- Integrates with Tasmota's `fast_loop`
|
||||
- Returns: `self`
|
||||
|
||||
**`stop()`**
|
||||
- Stops the engine and all animations
|
||||
- Returns: `self`
|
||||
|
||||
**`size()`**
|
||||
- Returns: Number of active animations
|
||||
|
||||
**`is_active()`**
|
||||
- Returns: `true` if engine is running
|
||||
|
||||
#### Example
|
||||
```berry
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
var pulse = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
|
||||
engine.add_animation(pulse).start()
|
||||
```
|
||||
|
||||
### Pattern (Base Class)
|
||||
|
||||
Base class for all visual elements.
|
||||
|
||||
#### Properties
|
||||
- **`priority`** (int) - Rendering priority (higher = on top)
|
||||
- **`opacity`** (int) - Opacity 0-255 for blending
|
||||
- **`name`** (string) - Pattern identification
|
||||
- **`is_running`** (bool) - Whether pattern is active
|
||||
|
||||
#### Methods
|
||||
|
||||
**`start()`** / **`stop()`**
|
||||
- Control pattern lifecycle
|
||||
- Returns: `self`
|
||||
|
||||
**`set_priority(priority)`**
|
||||
- Set rendering priority
|
||||
- Returns: `self`
|
||||
|
||||
**`set_opacity(opacity)`**
|
||||
- Set opacity (0-255)
|
||||
- Returns: `self`
|
||||
|
||||
### Animation (Extends Pattern)
|
||||
|
||||
Adds temporal behavior to patterns.
|
||||
|
||||
#### Additional Properties
|
||||
- **`duration`** (int) - Animation duration in ms (0 = infinite)
|
||||
- **`loop`** (bool) - Whether to loop when complete
|
||||
- **`start_time`** (int) - When animation started
|
||||
- **`current_time`** (int) - Current animation time
|
||||
|
||||
#### Additional Methods
|
||||
|
||||
**`set_duration(duration_ms)`**
|
||||
- Set animation duration
|
||||
- Returns: `self`
|
||||
|
||||
**`set_loop(loop)`**
|
||||
- Enable/disable looping
|
||||
- Returns: `self`
|
||||
|
||||
**`get_progress()`**
|
||||
- Returns: Animation progress (0-255)
|
||||
|
||||
## Animation Functions
|
||||
|
||||
### Basic Animations
|
||||
|
||||
**`animation.solid(color, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates solid color animation
|
||||
- **color**: ARGB color value (0xAARRGGBB) or ValueProvider instance
|
||||
- Returns: `PatternAnimation` instance
|
||||
|
||||
```berry
|
||||
var red = animation.solid(0xFFFF0000)
|
||||
var blue = animation.solid(0xFF0000FF, 10, 5000, true, 200, "blue_anim")
|
||||
var dynamic = animation.solid(animation.smooth(0xFF000000, 0xFFFFFFFF, 3000))
|
||||
```
|
||||
|
||||
**`animation.pulse(pattern, period_ms, min_brightness=0, max_brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates pulsing animation
|
||||
- **pattern**: Base pattern to pulse
|
||||
- **period_ms**: Pulse period in milliseconds
|
||||
- **min_brightness**: Minimum brightness (0-255)
|
||||
- **max_brightness**: Maximum brightness (0-255)
|
||||
- Returns: `PulseAnimation` instance
|
||||
|
||||
```berry
|
||||
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
```
|
||||
|
||||
**`animation.breathe(color, period_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates smooth breathing effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **period_ms**: Breathing period in milliseconds
|
||||
- Returns: `BreatheAnimation` instance
|
||||
|
||||
```berry
|
||||
var breathe_blue = animation.breathe(0xFF0000FF, 4000)
|
||||
var dynamic_breathe = animation.breathe(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00], 2000), 4000)
|
||||
```
|
||||
|
||||
### Palette-Based Animations
|
||||
|
||||
**`animation.rich_palette_animation(palette, period_ms, transition_type=1, brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates palette-based color cycling animation
|
||||
- **palette**: Palette in VRGB bytes format or palette name
|
||||
- **period_ms**: Cycle period in milliseconds
|
||||
- **transition_type**: 0=linear, 1=smooth (sine)
|
||||
- **brightness**: Overall brightness (0-255)
|
||||
- Returns: `FilledAnimation` instance
|
||||
|
||||
```berry
|
||||
var rainbow = animation.rich_palette_animation(animation.PALETTE_RAINBOW, 5000, 1, 255)
|
||||
```
|
||||
|
||||
### Position-Based Animations
|
||||
|
||||
**`animation.pulse_position_animation(color, pos, pulse_size, slew_size=0, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates pulse at specific position
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **pos**: Pixel position (0-based) or ValueProvider instance
|
||||
- **pulse_size**: Width of pulse in pixels or ValueProvider instance
|
||||
- **slew_size**: Fade region size in pixels or ValueProvider instance
|
||||
- Returns: `PulsePositionAnimation` instance
|
||||
|
||||
```berry
|
||||
var center_pulse = animation.pulse_position_animation(0xFFFFFFFF, 15, 3, 2)
|
||||
var moving_pulse = animation.pulse_position_animation(0xFFFF0000, animation.smooth(0, 29, 3000), 3, 2)
|
||||
```
|
||||
|
||||
**`animation.comet_animation(color, tail_length, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates moving comet effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **tail_length**: Length of comet tail in pixels
|
||||
- **speed_ms**: Movement speed in milliseconds per pixel
|
||||
- Returns: `CometAnimation` instance
|
||||
|
||||
```berry
|
||||
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
|
||||
var rainbow_comet = animation.comet_animation(animation.rich_palette_color_provider(animation.PALETTE_RAINBOW, 3000), 8, 100)
|
||||
```
|
||||
|
||||
**`animation.twinkle_animation(color, density, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates twinkling stars effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **density**: Number of twinkling pixels
|
||||
- **speed_ms**: Twinkle speed in milliseconds
|
||||
- Returns: `TwinkleAnimation` instance
|
||||
|
||||
```berry
|
||||
var stars = animation.twinkle_animation(0xFFFFFFFF, 5, 500)
|
||||
var color_changing_stars = animation.twinkle_animation(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00, 0xFF0000FF], 4000), 5, 500)
|
||||
```
|
||||
|
||||
### Fire and Natural Effects
|
||||
|
||||
**`animation.fire_animation(color=nil, intensity=200, speed_ms=100, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates realistic fire simulation
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for default fire palette
|
||||
- **intensity**: Fire intensity (0-255)
|
||||
- **speed_ms**: Animation speed in milliseconds
|
||||
- Returns: `FireAnimation` instance
|
||||
|
||||
```berry
|
||||
var fire = animation.fire_animation(nil, 180, 150) # Default fire palette
|
||||
var blue_fire = animation.fire_animation(0xFF0066FF, 180, 150) # Blue fire
|
||||
```
|
||||
|
||||
### Advanced Pattern Animations
|
||||
|
||||
**`animation.noise_rainbow(scale, speed, strip_length, priority)`**
|
||||
- Creates rainbow noise pattern with fractal complexity
|
||||
- **scale**: Noise frequency/detail (0-255, higher = more detail)
|
||||
- **speed**: Animation speed (0-255, 0 = static)
|
||||
- **strip_length**: LED strip length
|
||||
- **priority**: Rendering priority
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
**`animation.noise_single_color(color, scale, speed, strip_length, priority)`**
|
||||
- Creates single-color noise pattern
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
**`animation.noise_fractal(color, scale, speed, octaves, strip_length, priority)`**
|
||||
- Creates multi-octave fractal noise
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
|
||||
- **octaves**: Number of noise octaves (1-4)
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
```berry
|
||||
var rainbow_noise = animation.noise_rainbow(60, 40, 30, 10)
|
||||
var blue_noise = animation.noise_single_color(0xFF0066FF, 120, 60, 30, 10)
|
||||
var fractal = animation.noise_fractal(nil, 40, 50, 3, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.plasma_rainbow(time_speed, strip_length, priority)`**
|
||||
- Creates rainbow plasma effect using sine wave interference
|
||||
- **time_speed**: Animation speed (0-255)
|
||||
- Returns: `PlasmaAnimation` instance
|
||||
|
||||
**`animation.plasma_single_color(color, time_speed, strip_length, priority)`**
|
||||
- Creates single-color plasma effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `PlasmaAnimation` instance
|
||||
|
||||
```berry
|
||||
var plasma = animation.plasma_rainbow(80, 30, 10)
|
||||
var purple_plasma = animation.plasma_single_color(0xFF8800FF, 60, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.sparkle_white(density, fade_speed, strip_length, priority)`**
|
||||
- Creates white twinkling sparkles
|
||||
- **density**: Sparkle creation probability (0-255)
|
||||
- **fade_speed**: Fade-out speed (0-255)
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
**`animation.sparkle_colored(color, density, fade_speed, strip_length, priority)`**
|
||||
- Creates colored sparkles
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
**`animation.sparkle_rainbow(density, fade_speed, strip_length, priority)`**
|
||||
- Creates rainbow sparkles
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
```berry
|
||||
var white_sparkles = animation.sparkle_white(80, 60, 30, 10)
|
||||
var red_sparkles = animation.sparkle_colored(0xFFFF0000, 100, 50, 30, 10)
|
||||
var rainbow_sparkles = animation.sparkle_rainbow(60, 40, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.wave_rainbow_sine(amplitude, wave_speed, strip_length, priority)`**
|
||||
- Creates rainbow sine wave pattern
|
||||
- **amplitude**: Wave amplitude/intensity (0-255)
|
||||
- **wave_speed**: Wave movement speed (0-255)
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
**`animation.wave_single_sine(color, amplitude, wave_speed, strip_length, priority)`**
|
||||
- Creates single-color sine wave
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
**`animation.wave_custom(color, wave_type, amplitude, frequency, strip_length, priority)`**
|
||||
- Creates custom wave with specified type
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
|
||||
- **wave_type**: 0=sine, 1=triangle, 2=square, 3=sawtooth
|
||||
- **frequency**: Wave frequency/density (0-255)
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
```berry
|
||||
var sine_wave = animation.wave_rainbow_sine(40, 80, 30, 10)
|
||||
var green_wave = animation.wave_single_sine(0xFF00FF00, 60, 40, 30, 10)
|
||||
var triangle_wave = animation.wave_custom(nil, 1, 50, 70, 30, 10)
|
||||
```
|
||||
|
||||
### Motion Effect Animations
|
||||
|
||||
Motion effects transform existing animations by applying movement, scaling, and distortion effects.
|
||||
|
||||
**`animation.shift_scroll_right(source, speed, strip_length, priority)`**
|
||||
- Scrolls animation to the right with wrapping
|
||||
- **source**: Source animation to transform
|
||||
- **speed**: Scroll speed (0-255)
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
**`animation.shift_scroll_left(source, speed, strip_length, priority)`**
|
||||
- Scrolls animation to the left with wrapping
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
**`animation.shift_bounce_horizontal(source, speed, strip_length, priority)`**
|
||||
- Bounces animation horizontally at strip edges
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
```berry
|
||||
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
|
||||
var scrolling = animation.shift_scroll_right(base, 100, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.bounce_gravity(source, speed, gravity, strip_length, priority)`**
|
||||
- Physics-based bouncing with gravity simulation
|
||||
- **source**: Source animation to transform
|
||||
- **speed**: Initial bounce speed (0-255)
|
||||
- **gravity**: Gravity strength (0-255)
|
||||
- Returns: `BounceAnimation` instance
|
||||
|
||||
**`animation.bounce_basic(source, speed, damping, strip_length, priority)`**
|
||||
- Basic bouncing without gravity
|
||||
- **damping**: Damping factor (0-255, 255=no damping)
|
||||
- Returns: `BounceAnimation` instance
|
||||
|
||||
```berry
|
||||
var sparkles = animation.sparkle_white(80, 50, 30, 5)
|
||||
var bouncing = animation.bounce_gravity(sparkles, 150, 80, 30, 10)
|
||||
var elastic = animation.bounce_basic(sparkles, 120, 240, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.scale_static(source, scale_factor, strip_length, priority)`**
|
||||
- Static scaling of animation
|
||||
- **source**: Source animation to transform
|
||||
- **scale_factor**: Scale factor (128=1.0x, 64=0.5x, 255=2.0x)
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
**`animation.scale_oscillate(source, speed, strip_length, priority)`**
|
||||
- Oscillating scale (breathing effect)
|
||||
- **speed**: Oscillation speed (0-255)
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
**`animation.scale_grow(source, speed, strip_length, priority)`**
|
||||
- Growing scale effect
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
```berry
|
||||
var pattern = animation.gradient_rainbow_linear(0, 30, 5)
|
||||
var breathing = animation.scale_oscillate(pattern, 60, 30, 10)
|
||||
var zoomed = animation.scale_static(pattern, 180, 30, 10) # 1.4x scale
|
||||
```
|
||||
|
||||
**`animation.jitter_position(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random position shake effects
|
||||
- **source**: Source animation to transform
|
||||
- **intensity**: Jitter intensity (0-255)
|
||||
- **frequency**: Jitter frequency (0-255, maps to 0-30 Hz)
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_color(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random color variations
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_brightness(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random brightness changes
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_all(source, intensity, frequency, strip_length, priority)`**
|
||||
- Combination of position, color, and brightness jitter
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
```berry
|
||||
var base = animation.gradient_rainbow_linear(0, 30, 5)
|
||||
var glitch = animation.jitter_all(base, 120, 100, 30, 15)
|
||||
var shake = animation.jitter_position(base, 60, 40, 30, 10)
|
||||
```
|
||||
|
||||
### Chaining Motion Effects
|
||||
|
||||
Motion effects can be chained together for complex transformations:
|
||||
|
||||
```berry
|
||||
# Base animation
|
||||
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
|
||||
|
||||
# Apply multiple transformations
|
||||
var scaled = animation.scale_static(base, 150, 30, 8) # 1.2x scale
|
||||
var shifted = animation.shift_scroll_left(scaled, 60, 30, 12) # Scroll left
|
||||
var jittered = animation.jitter_color(shifted, 40, 30, 30, 15) # Add color jitter
|
||||
|
||||
# Result: A scaled, scrolling, color-jittered pulse
|
||||
```
|
||||
|
||||
## Color System
|
||||
|
||||
### Color Formats
|
||||
|
||||
**ARGB Format**: `0xAARRGGBB`
|
||||
- **AA**: Alpha channel (opacity) - usually `FF` for opaque
|
||||
- **RR**: Red component (00-FF)
|
||||
- **GG**: Green component (00-FF)
|
||||
- **BB**: Blue component (00-FF)
|
||||
|
||||
```berry
|
||||
var red = 0xFFFF0000 # Opaque red
|
||||
var semi_blue = 0x800000FF # Semi-transparent blue
|
||||
var white = 0xFFFFFFFF # Opaque white
|
||||
var black = 0xFF000000 # Opaque black
|
||||
```
|
||||
|
||||
### Predefined Colors
|
||||
|
||||
```berry
|
||||
# Available as constants
|
||||
animation.COLOR_RED # 0xFFFF0000
|
||||
animation.COLOR_GREEN # 0xFF00FF00
|
||||
animation.COLOR_BLUE # 0xFF0000FF
|
||||
animation.COLOR_WHITE # 0xFFFFFFFF
|
||||
animation.COLOR_BLACK # 0xFF000000
|
||||
```
|
||||
|
||||
### Palette System
|
||||
|
||||
**Creating Palettes**
|
||||
```berry
|
||||
# VRGB format: Value(position), Red, Green, Blue
|
||||
var fire_palette = bytes("00000000" "80FF0000" "FFFFFF00")
|
||||
# ^pos=0 ^pos=128 ^pos=255
|
||||
# black red yellow
|
||||
```
|
||||
|
||||
**Predefined Palettes**
|
||||
```berry
|
||||
animation.PALETTE_RAINBOW # Standard rainbow colors
|
||||
animation.PALETTE_FIRE # Fire effect colors
|
||||
animation.PALETTE_OCEAN # Ocean wave colors
|
||||
```
|
||||
|
||||
## Value Providers
|
||||
|
||||
Dynamic parameters that change over time.
|
||||
|
||||
### Static Values
|
||||
```berry
|
||||
# Regular values are automatically wrapped
|
||||
var static_color = 0xFFFF0000
|
||||
var static_position = 15
|
||||
```
|
||||
|
||||
### Oscillator Providers
|
||||
|
||||
**`animation.smooth(start, end, period_ms)`**
|
||||
- Smooth cosine wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.linear(start, end, period_ms)`**
|
||||
- Triangle wave oscillation (goes from start to end, then back to start)
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.triangle(start, end, period_ms)`**
|
||||
- Alias for `linear()` - triangle wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.ramp(start, end, period_ms)`**
|
||||
- Sawtooth wave oscillation (linear progression from start to end)
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.sawtooth(start, end, period_ms)`**
|
||||
- Alias for `ramp()` - sawtooth wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.square(start, end, period_ms, duty_cycle=50)`**
|
||||
- Square wave oscillation
|
||||
- **duty_cycle**: Percentage of time at high value
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
```berry
|
||||
# Dynamic position that moves back and forth
|
||||
var moving_pos = animation.smooth(0, 29, 3000)
|
||||
|
||||
# Dynamic color that cycles brightness
|
||||
var breathing_color = animation.smooth(50, 255, 2000)
|
||||
|
||||
# Use with animations
|
||||
var dynamic_pulse = animation.pulse_position_animation(
|
||||
0xFFFF0000, # Static red color
|
||||
moving_pos, # Dynamic position
|
||||
3, # Static pulse size
|
||||
1 # Static slew size
|
||||
)
|
||||
```
|
||||
|
||||
## Event System
|
||||
|
||||
### Event Registration
|
||||
|
||||
**`animation.register_event_handler(event_name, callback, priority=0, condition=nil, metadata=nil)`**
|
||||
- Registers an event handler
|
||||
- **event_name**: Name of event to handle
|
||||
- **callback**: Function to call when event occurs
|
||||
- **priority**: Handler priority (higher = executed first)
|
||||
- **condition**: Optional condition function
|
||||
- **metadata**: Optional metadata map
|
||||
- Returns: `EventHandler` instance
|
||||
|
||||
```berry
|
||||
def flash_white(event_data)
|
||||
var flash = animation.solid(0xFFFFFFFF)
|
||||
engine.add_animation(flash)
|
||||
end
|
||||
|
||||
var handler = animation.register_event_handler("button_press", flash_white, 10)
|
||||
```
|
||||
|
||||
### Event Triggering
|
||||
|
||||
**`animation.trigger_event(event_name, event_data={})`**
|
||||
- Triggers an event
|
||||
- **event_name**: Name of event to trigger
|
||||
- **event_data**: Data to pass to handlers
|
||||
|
||||
```berry
|
||||
animation.trigger_event("button_press", {"button": "main"})
|
||||
```
|
||||
|
||||
## DSL System
|
||||
|
||||
### DSL Runtime
|
||||
|
||||
**`animation.DSLRuntime(engine, debug_mode=false)`**
|
||||
- Creates DSL runtime instance
|
||||
- **engine**: AnimationEngine instance
|
||||
- **debug_mode**: Enable debug output
|
||||
- Returns: `DSLRuntime` instance
|
||||
|
||||
#### Methods
|
||||
|
||||
**`load_dsl(source_code)`**
|
||||
- Compiles and executes DSL source code
|
||||
- **source_code**: DSL source as string
|
||||
- Returns: `true` on success, `false` on error
|
||||
|
||||
**`load_dsl_file(filename)`**
|
||||
- Loads and executes DSL from file
|
||||
- **filename**: Path to .anim file
|
||||
- Returns: `true` on success, `false` on error
|
||||
|
||||
```berry
|
||||
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
|
||||
|
||||
var dsl_code = '''
|
||||
color red = #FF0000
|
||||
animation pulse_red = pulse(solid(red), 2s, 50%, 100%)
|
||||
run pulse_red
|
||||
'''
|
||||
|
||||
if runtime.load_dsl(dsl_code)
|
||||
print("Animation loaded successfully")
|
||||
else
|
||||
print("Failed to load animation")
|
||||
end
|
||||
```
|
||||
|
||||
### DSL Compilation
|
||||
|
||||
**`animation.compile_dsl(source_code)`**
|
||||
- Compiles DSL to Berry code
|
||||
- **source_code**: DSL source as string
|
||||
- Returns: Berry code string or raises exception
|
||||
- Raises: `"dsl_compilation_error"` on compilation failure
|
||||
|
||||
```berry
|
||||
try
|
||||
var berry_code = animation.compile_dsl(dsl_source)
|
||||
print("Generated code:", berry_code)
|
||||
var compiled_func = compile(berry_code)
|
||||
compiled_func()
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("Compilation error:", msg)
|
||||
end
|
||||
```
|
||||
|
||||
## User Functions
|
||||
|
||||
### Function Registration
|
||||
|
||||
**`animation.register_user_function(name, func)`**
|
||||
- Registers Berry function for DSL use
|
||||
- **name**: Function name for DSL
|
||||
- **func**: Berry function to register
|
||||
|
||||
**`animation.is_user_function(name)`**
|
||||
- Checks if function is registered
|
||||
- Returns: `true` if registered
|
||||
|
||||
**`animation.get_user_function(name)`**
|
||||
- Gets registered function
|
||||
- Returns: Function or `nil`
|
||||
|
||||
**`animation.list_user_functions()`**
|
||||
- Lists all registered function names
|
||||
- Returns: Array of function names
|
||||
|
||||
```berry
|
||||
def custom_breathing(color, period)
|
||||
return animation.pulse(animation.solid(color), period, 50, 255)
|
||||
end
|
||||
|
||||
animation.register_user_function("breathing", custom_breathing)
|
||||
|
||||
# Now available in DSL:
|
||||
# animation my_effect = breathing(red, 3s)
|
||||
```
|
||||
|
||||
## Version Information
|
||||
|
||||
The framework uses a numeric version system for efficient comparison:
|
||||
|
||||
```berry
|
||||
# Primary version (0xAABBCCDD format: AA=major, BB=minor, CC=patch, DD=build)
|
||||
print(f"0x{animation.VERSION:08X}") # 0x00010000
|
||||
|
||||
# Convert to string format (drops build number)
|
||||
print(animation.version_string()) # "0.1.0"
|
||||
|
||||
# Convert any version number to string
|
||||
print(animation.version_string(0x01020304)) # "1.2.3"
|
||||
|
||||
# Extract components manually
|
||||
var major = (animation.VERSION >> 24) & 0xFF # 0
|
||||
var minor = (animation.VERSION >> 16) & 0xFF # 1
|
||||
var patch = (animation.VERSION >> 8) & 0xFF # 0
|
||||
var build = animation.VERSION & 0xFF # 0
|
||||
|
||||
# Version comparison
|
||||
var is_new_enough = animation.VERSION >= 0x00010000 # v0.1.0+
|
||||
```
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Global Variable Access
|
||||
|
||||
**`animation.global(name)`**
|
||||
- Safely accesses global variables
|
||||
- **name**: Variable name
|
||||
- Returns: Variable value
|
||||
- Raises: `"syntax_error"` if variable doesn't exist
|
||||
|
||||
```berry
|
||||
# Set global variable
|
||||
global.my_color = 0xFFFF0000
|
||||
|
||||
# Access safely
|
||||
var color = animation.global("my_color") # Returns 0xFFFF0000
|
||||
var missing = animation.global("missing") # Raises exception
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
**`animation.is_value_provider(obj)`**
|
||||
- Checks if object is a ValueProvider
|
||||
- Returns: `true` if object implements ValueProvider interface
|
||||
|
||||
**`animation.is_color_provider(obj)`**
|
||||
- Checks if object is a ColorProvider
|
||||
- Returns: `true` if object implements ColorProvider interface
|
||||
|
||||
```berry
|
||||
var static_val = 42
|
||||
var dynamic_val = animation.smooth(0, 100, 2000)
|
||||
|
||||
print(animation.is_value_provider(static_val)) # false
|
||||
print(animation.is_value_provider(dynamic_val)) # true
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Exceptions
|
||||
|
||||
- **`"dsl_compilation_error"`** - DSL compilation failed
|
||||
- **`"syntax_error"`** - Variable not found or syntax error
|
||||
- **`"type_error"`** - Invalid parameter type
|
||||
- **`"runtime_error"`** - General runtime error
|
||||
|
||||
### Best Practices
|
||||
|
||||
```berry
|
||||
# Always use try/catch for DSL operations
|
||||
try
|
||||
runtime.load_dsl(dsl_code)
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("DSL Error:", msg)
|
||||
except .. as e, msg
|
||||
print("Unexpected error:", msg)
|
||||
end
|
||||
|
||||
# Check engine state before operations
|
||||
if engine.is_active()
|
||||
engine.add_animation(new_animation)
|
||||
else
|
||||
print("Engine not running")
|
||||
end
|
||||
|
||||
# Validate parameters
|
||||
if type(color) == "int" && color >= 0
|
||||
var anim = animation.solid(color)
|
||||
else
|
||||
print("Invalid color value")
|
||||
end
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Memory Management
|
||||
```berry
|
||||
# Clear animations when switching effects
|
||||
engine.clear()
|
||||
engine.add_animation(new_animation)
|
||||
|
||||
# Reuse animation objects when possible
|
||||
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
# Use pulse_red multiple times instead of creating new instances
|
||||
```
|
||||
|
||||
### Timing Optimization
|
||||
```berry
|
||||
# Use longer periods for smoother performance
|
||||
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
|
||||
var choppy_pulse = animation.pulse(pattern, 100, 50, 255) # 100ms - may be choppy
|
||||
|
||||
# Limit simultaneous animations
|
||||
# Good: 1-3 animations
|
||||
# Avoid: 10+ animations running simultaneously
|
||||
```
|
||||
|
||||
### Value Provider Efficiency
|
||||
```berry
|
||||
# Efficient: Reuse providers
|
||||
var breathing = animation.smooth(50, 255, 2000)
|
||||
var anim1 = animation.pulse(pattern1, breathing)
|
||||
var anim2 = animation.pulse(pattern2, breathing) # Reuse same provider
|
||||
|
||||
# Inefficient: Create new providers
|
||||
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
|
||||
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000)) # Duplicate
|
||||
```
|
||||
|
||||
This API reference covers the essential classes and functions. For more advanced usage, see the [Examples](EXAMPLES.md) and [User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md) documentation.
|
||||
696
lib/libesp32/berry_animation/docs/DSL_REFERENCE.md
Normal file
696
lib/libesp32/berry_animation/docs/DSL_REFERENCE.md
Normal file
@ -0,0 +1,696 @@
|
||||
# DSL Reference - Animation DSL Language Specification
|
||||
|
||||
This document provides a comprehensive reference for the Animation DSL syntax, keywords, and grammar. It focuses purely on the language specification without implementation details.
|
||||
|
||||
## Language Overview
|
||||
|
||||
The Animation DSL is a declarative language for defining LED strip animations. It uses natural, readable syntax with named parameters and supports colors, animations, sequences, and property assignments.
|
||||
|
||||
## Comments
|
||||
|
||||
Comments use the `#` character and extend to the end of the line:
|
||||
|
||||
```dsl
|
||||
# This is a full-line comment
|
||||
strip length 30 # This is an inline comment
|
||||
```
|
||||
|
||||
Comments are preserved in the generated code and can appear anywhere in the DSL.
|
||||
|
||||
## Program Structure
|
||||
|
||||
A DSL program consists of statements that can appear in any order:
|
||||
|
||||
```dsl
|
||||
# Optional strip configuration (must be first if present)
|
||||
strip length 60
|
||||
|
||||
# Color definitions
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Animation definitions
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
|
||||
# Property assignments
|
||||
pulse_red.priority = 10
|
||||
|
||||
# Sequences
|
||||
sequence demo {
|
||||
play pulse_red for 5s
|
||||
wait 1s
|
||||
}
|
||||
|
||||
# Execution
|
||||
run demo
|
||||
```
|
||||
|
||||
## Keywords
|
||||
|
||||
### Reserved Keywords
|
||||
|
||||
The following keywords are reserved and cannot be used as identifiers:
|
||||
|
||||
**Configuration Keywords:**
|
||||
- `strip` - Strip configuration
|
||||
- `set` - Variable assignment
|
||||
|
||||
**Definition Keywords:**
|
||||
- `color` - Color definition
|
||||
- `palette` - Palette definition
|
||||
- `animation` - Animation definition
|
||||
- `sequence` - Sequence definition
|
||||
|
||||
**Control Flow Keywords:**
|
||||
- `play` - Play animation in sequence
|
||||
- `wait` - Wait/pause in sequence
|
||||
- `repeat` - Repeat loop
|
||||
- `times` - Loop count specifier
|
||||
- `for` - Duration specifier
|
||||
- `run` - Execute animation or sequence
|
||||
|
||||
**Easing Keywords:**
|
||||
- `linear` - Linear/triangle wave easing
|
||||
- `triangle` - Triangle wave easing (alias for linear)
|
||||
- `smooth` - Smooth cosine easing
|
||||
- `sine` - Pure sine wave easing
|
||||
- `ease_in` - Ease in transition (quadratic acceleration)
|
||||
- `ease_out` - Ease out transition (quadratic deceleration)
|
||||
- `ramp` - Ramp/sawtooth easing
|
||||
- `sawtooth` - Sawtooth easing (alias for ramp)
|
||||
- `square` - Square wave easing
|
||||
- `elastic` - Elastic easing with spring-like overshoot
|
||||
- `bounce` - Bounce easing like a ball with decreasing amplitude
|
||||
|
||||
**Value Keywords:**
|
||||
- `true` - Boolean true
|
||||
- `false` - Boolean false
|
||||
- `nil` - Null value
|
||||
- `transparent` - Transparent color
|
||||
|
||||
### Predefined Colors
|
||||
|
||||
The following color names are predefined and cannot be redefined:
|
||||
|
||||
**Primary Colors:**
|
||||
- `red`, `green`, `blue`
|
||||
- `white`, `black`
|
||||
|
||||
**Extended Colors:**
|
||||
- `yellow`, `orange`, `purple`, `pink`
|
||||
- `cyan`, `magenta`, `gray`, `grey`
|
||||
- `silver`, `gold`, `brown`
|
||||
- `lime`, `navy`, `olive`
|
||||
- `maroon`, `teal`, `aqua`
|
||||
- `fuchsia`, `indigo`, `violet`
|
||||
- `crimson`, `coral`, `salmon`
|
||||
- `khaki`, `plum`, `orchid`
|
||||
- `turquoise`, `tan`, `beige`
|
||||
- `ivory`, `snow`
|
||||
- `transparent`
|
||||
|
||||
## Data Types
|
||||
|
||||
### Numbers
|
||||
|
||||
```dsl
|
||||
42 # Integer
|
||||
3.14 # Floating point
|
||||
-5 # Negative number
|
||||
```
|
||||
|
||||
### Time Values
|
||||
|
||||
Time values require a unit suffix and are automatically converted to milliseconds:
|
||||
|
||||
```dsl
|
||||
500ms # Milliseconds (stays 500)
|
||||
2s # Seconds (converted to 2000ms)
|
||||
1m # Minutes (converted to 60000ms)
|
||||
1h # Hours (converted to 3600000ms)
|
||||
```
|
||||
|
||||
### Percentages
|
||||
|
||||
Percentages use the `%` suffix and are automatically converted to 0-255 range with possible over-shooting:
|
||||
|
||||
```dsl
|
||||
0% # 0 percent (converted to 0)
|
||||
50% # 50 percent (converted to 128)
|
||||
100% # 100 percent (converted to 255)
|
||||
120% # 120 percent (converted to 306)
|
||||
```
|
||||
|
||||
### Colors
|
||||
|
||||
#### Hexadecimal Colors
|
||||
|
||||
```dsl
|
||||
0xFF0000 # Red (RGB format)
|
||||
0x80FF0000 # Semi-transparent red (ARGB format)
|
||||
```
|
||||
|
||||
#### Named Colors
|
||||
|
||||
```dsl
|
||||
red # Predefined color name
|
||||
blue # Predefined color name
|
||||
transparent # Transparent color
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
```dsl
|
||||
"hello" # Double-quoted string
|
||||
'world' # Single-quoted string
|
||||
```
|
||||
|
||||
### Identifiers
|
||||
|
||||
Identifiers must start with a letter or underscore, followed by letters, digits, or underscores:
|
||||
|
||||
```dsl
|
||||
my_color # Valid identifier
|
||||
_private_var # Valid identifier
|
||||
Color123 # Valid identifier
|
||||
```
|
||||
|
||||
## Configuration Statements
|
||||
|
||||
### Strip Configuration
|
||||
|
||||
The `strip` statement configures the LED strip and must be the first statement if present:
|
||||
|
||||
```dsl
|
||||
strip length 60 # Set strip length to 60 LEDs
|
||||
```
|
||||
|
||||
If omitted, the system uses the configured strip length from the host system.
|
||||
|
||||
### Variable Assignment
|
||||
|
||||
The `set` keyword assigns static values or value providers to global variables:
|
||||
|
||||
```dsl
|
||||
set brightness = 200 # Static integer value
|
||||
set cycle_time = 5s # Static time value (converted to 5000ms)
|
||||
set opacity_level = 80% # Static percentage (converted to 204)
|
||||
|
||||
# Value providers for dynamic values
|
||||
set brightness_osc = smooth(min_value=50, max_value=255, period=3s)
|
||||
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
|
||||
```
|
||||
|
||||
## Color Definitions
|
||||
|
||||
The `color` keyword defines static colors or color providers:
|
||||
|
||||
```dsl
|
||||
# Static colors
|
||||
color red = 0xFF0000 # Static hex color
|
||||
color blue = 0x0000FF # Static hex color
|
||||
color semi_red = 0x80FF0000 # Static color with alpha channel
|
||||
color my_white = white # Reference to predefined color
|
||||
|
||||
# Color providers for dynamic colors
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=[red, green, blue],
|
||||
cycle_period=5s
|
||||
)
|
||||
color breathing_red = breathe_color(
|
||||
base_color=red,
|
||||
min_brightness=20,
|
||||
max_brightness=255,
|
||||
duration=3s,
|
||||
curve_factor=2
|
||||
)
|
||||
color pulsing_blue = pulsating_color(
|
||||
base_color=blue,
|
||||
min_brightness=50,
|
||||
max_brightness=200,
|
||||
duration=1s
|
||||
)
|
||||
```
|
||||
|
||||
## Palette Definitions
|
||||
|
||||
Palettes define color gradients using position-color pairs and support two encoding formats:
|
||||
|
||||
### Value-Based Palettes (Recommended)
|
||||
|
||||
Standard palettes use value positions from 0-255:
|
||||
|
||||
```dsl
|
||||
palette fire_colors = [
|
||||
(0, 0x000000), # Position 0: Black
|
||||
(128, 0xFF0000), # Position 128: Red
|
||||
(255, 0xFFFF00) # Position 255: Yellow
|
||||
]
|
||||
|
||||
palette ocean_palette = [
|
||||
(0, navy), # Using named colors
|
||||
(128, cyan),
|
||||
(255, green)
|
||||
]
|
||||
```
|
||||
|
||||
### Tick-Based Palettes (Advanced)
|
||||
|
||||
Palettes can also use tick counts for timing-based transitions:
|
||||
|
||||
```dsl
|
||||
palette timed_colors = [
|
||||
(10, 0xFF0000), # Red for 10 ticks
|
||||
(20, 0x00FF00), # Green for 20 ticks
|
||||
(15, 0x0000FF) # Blue for 15 ticks
|
||||
]
|
||||
```
|
||||
|
||||
**Palette Rules:**
|
||||
- **Value-based**: Positions range from 0 to 255, represent intensity/brightness levels
|
||||
- **Tick-based**: Positions represent duration in arbitrary time units
|
||||
- Colors can be hex values or named colors
|
||||
- Entries are automatically sorted by position
|
||||
- Comments are preserved
|
||||
- Automatically converted to efficient VRGB bytes format
|
||||
|
||||
## Animation Definitions
|
||||
|
||||
The `animation` keyword defines instances of animation classes (subclasses of Animation):
|
||||
|
||||
```dsl
|
||||
animation red_solid = solid(color=red)
|
||||
|
||||
animation pulse_effect = pulsating_animation(
|
||||
color=blue,
|
||||
period=2s
|
||||
)
|
||||
|
||||
animation comet_trail = comet_animation(
|
||||
color=white,
|
||||
tail_length=10,
|
||||
speed=1500,
|
||||
direction=1
|
||||
)
|
||||
```
|
||||
|
||||
**Parameter Syntax:**
|
||||
- All parameters use `name=value` format
|
||||
- Parameters can reference colors, other animations, or literal values
|
||||
- Nested function calls are supported
|
||||
|
||||
## Property Assignments
|
||||
|
||||
Animation properties can be modified after creation:
|
||||
|
||||
```dsl
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
|
||||
# Set properties
|
||||
pulse_red.priority = 10
|
||||
pulse_red.opacity = 200
|
||||
pulse_red.position = 15
|
||||
|
||||
# Dynamic properties using value providers
|
||||
pulse_red.position = triangle(min_value=0, max_value=29, period=5s)
|
||||
pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
|
||||
```
|
||||
|
||||
**Common Properties:**
|
||||
- `priority` - Animation priority (higher numbers have precedence)
|
||||
- `opacity` - Opacity level (0-255)
|
||||
- `position` - Position on strip
|
||||
- `speed` - Speed multiplier
|
||||
- `phase` - Phase offset
|
||||
|
||||
## Sequences
|
||||
|
||||
Sequences orchestrate multiple animations with timing control:
|
||||
|
||||
```dsl
|
||||
sequence demo {
|
||||
play red_animation for 3s
|
||||
wait 1s
|
||||
play blue_animation for 2s
|
||||
|
||||
repeat 3 times:
|
||||
play flash_effect for 200ms
|
||||
wait 300ms
|
||||
|
||||
play final_animation
|
||||
}
|
||||
```
|
||||
|
||||
### Sequence Statements
|
||||
|
||||
#### Play Statement
|
||||
|
||||
```dsl
|
||||
play animation_name # Play indefinitely
|
||||
play animation_name for 5s # Play for specific duration
|
||||
```
|
||||
|
||||
#### Wait Statement
|
||||
|
||||
```dsl
|
||||
wait 1s # Wait for 1 second
|
||||
wait 500ms # Wait for 500 milliseconds
|
||||
```
|
||||
|
||||
#### Repeat Statement
|
||||
|
||||
```dsl
|
||||
repeat 5 times:
|
||||
play effect for 1s
|
||||
wait 500ms
|
||||
|
||||
# Nested repeats are supported
|
||||
repeat 3 times:
|
||||
play intro for 2s
|
||||
repeat 2 times:
|
||||
play flash for 100ms
|
||||
wait 200ms
|
||||
play outro for 1s
|
||||
```
|
||||
|
||||
## Execution Statements
|
||||
|
||||
Execute animations or sequences:
|
||||
|
||||
```dsl
|
||||
run animation_name # Run an animation
|
||||
run sequence_name # Run a sequence
|
||||
```
|
||||
|
||||
## Operators and Expressions
|
||||
|
||||
### Arithmetic Operators
|
||||
|
||||
```dsl
|
||||
+ # Addition
|
||||
- # Subtraction (also unary minus)
|
||||
* # Multiplication
|
||||
/ # Division
|
||||
% # Modulo
|
||||
```
|
||||
|
||||
### Comparison Operators
|
||||
|
||||
```dsl
|
||||
== # Equal to
|
||||
!= # Not equal to
|
||||
< # Less than
|
||||
<= # Less than or equal
|
||||
> # Greater than
|
||||
>= # Greater than or equal
|
||||
```
|
||||
|
||||
### Logical Operators
|
||||
|
||||
```dsl
|
||||
&& # Logical AND
|
||||
|| # Logical OR
|
||||
! # Logical NOT
|
||||
```
|
||||
|
||||
### Assignment Operators
|
||||
|
||||
```dsl
|
||||
= # Simple assignment
|
||||
```
|
||||
|
||||
## Function Calls
|
||||
|
||||
Functions use named parameter syntax:
|
||||
|
||||
```dsl
|
||||
function_name(param1=value1, param2=value2)
|
||||
|
||||
# Examples
|
||||
solid(color=red)
|
||||
pulsating_animation(color=blue, period=2s)
|
||||
triangle(min_value=0, max_value=255, period=3s)
|
||||
```
|
||||
|
||||
**Nested Function Calls:**
|
||||
```dsl
|
||||
pulsating_animation(
|
||||
color=solid(color=red),
|
||||
period=smooth(min_value=1000, max_value=3000, period=10s)
|
||||
)
|
||||
```
|
||||
|
||||
## Supported Classes
|
||||
|
||||
### Value Providers
|
||||
|
||||
Value providers create dynamic values that change over time:
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `static_value` | Returns a constant value |
|
||||
| `oscillator_value` | Oscillates between min/max values with various waveforms |
|
||||
|
||||
**Oscillator Aliases:**
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `triangle` | Triangle wave oscillation (alias for oscillator with triangle waveform) |
|
||||
| `smooth` | Smooth cosine wave (alias for oscillator with smooth waveform) |
|
||||
| `sine` | Pure sine wave oscillation (alias for oscillator with sine waveform) |
|
||||
| `linear` | Linear progression (alias for oscillator with linear waveform) |
|
||||
| `ramp` | Sawtooth wave (alias for oscillator with ramp waveform) |
|
||||
| `sawtooth` | Sawtooth wave (alias for ramp) |
|
||||
| `square` | Square wave oscillation |
|
||||
| `ease_in` | Quadratic ease-in (starts slow, accelerates) |
|
||||
| `ease_out` | Quadratic ease-out (starts fast, decelerates) |
|
||||
| `elastic` | Elastic easing with spring-like overshoot |
|
||||
| `bounce` | Bounce easing like a ball with decreasing amplitude |
|
||||
|
||||
```dsl
|
||||
# Direct oscillator usage
|
||||
triangle(min_value=0, max_value=255, period=2s) # Triangle wave
|
||||
smooth(min_value=50, max_value=200, period=3s) # Smooth cosine
|
||||
sine(min_value=0, max_value=255, period=2s) # Pure sine wave
|
||||
linear(min_value=0, max_value=100, period=1s) # Linear progression
|
||||
ramp(min_value=0, max_value=255, period=2s) # Sawtooth wave
|
||||
square(min_value=0, max_value=255, period=1s) # Square wave
|
||||
ease_in(min_value=0, max_value=255, period=2s) # Quadratic ease-in
|
||||
ease_out(min_value=0, max_value=255, period=2s) # Quadratic ease-out
|
||||
elastic(min_value=0, max_value=255, period=2s) # Elastic spring effect
|
||||
bounce(min_value=0, max_value=255, period=2s) # Bouncing ball effect
|
||||
|
||||
# Value providers can be assigned to variables
|
||||
set brightness_oscillator = smooth(min_value=50, max_value=255, period=3s)
|
||||
set position_sweep = triangle(min_value=0, max_value=29, period=5s)
|
||||
set elastic_movement = elastic(min_value=0, max_value=30, period=4s)
|
||||
```
|
||||
|
||||
### Color Providers
|
||||
|
||||
Color providers create dynamic colors that change over time:
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `static_color` | Solid color with optional dynamic opacity |
|
||||
| `color_cycle` | Cycles through a palette of colors |
|
||||
| `rich_palette` | Advanced palette-based color cycling with smooth transitions |
|
||||
| `composite_color` | Combines multiple color providers |
|
||||
| `breathe_color` | Breathing/pulsing color effect with brightness modulation |
|
||||
| `pulsating_color` | Fast pulsing color effect (alias for breathe_color with curve_factor=1) |
|
||||
|
||||
### Animation Classes
|
||||
|
||||
Animation classes create visual effects on LED strips:
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `solid` | Solid color fill |
|
||||
| `pulsating_animation` | Pulsing brightness effect |
|
||||
| `beacon_animation` | Positioned pulse effect |
|
||||
| `crenel_position_animation` | Square wave pulse at specific position |
|
||||
| `breathe_animation` | Breathing/fading effect |
|
||||
| `comet_animation` | Moving comet with trailing tail |
|
||||
| `fire_animation` | Realistic fire simulation |
|
||||
| `twinkle_animation` | Twinkling stars effect |
|
||||
| `gradient_animation` | Color gradient effects |
|
||||
| `noise_animation` | Perlin noise-based patterns |
|
||||
| `plasma_animation` | Plasma wave effects |
|
||||
| `sparkle_animation` | Sparkling/glitter effects |
|
||||
| `wave_animation` | Wave propagation effects |
|
||||
| `shift_animation` | Shifting/scrolling patterns |
|
||||
| `bounce_animation` | Bouncing ball effects |
|
||||
| `scale_animation` | Scaling/zooming effects |
|
||||
| `jitter_animation` | Random jitter/shake effects |
|
||||
| `rich_palette_animation` | Palette-based color cycling |
|
||||
| `palette_wave_animation` | Wave patterns using palettes |
|
||||
| `palette_gradient_animation` | Gradient patterns using palettes |
|
||||
| `palette_meter_animation` | Meter/bar patterns using palettes |
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Validation Rules
|
||||
|
||||
The DSL performs comprehensive validation at compile time:
|
||||
|
||||
1. **Reserved Names**: Cannot redefine keywords or predefined colors
|
||||
2. **Class Existence**: Animation and color provider factory functions must exist
|
||||
3. **Parameter Validation**: Function parameters must exist and be valid for the specific class
|
||||
4. **Type Checking**: Values must match expected types
|
||||
5. **Reference Resolution**: All referenced identifiers must be defined
|
||||
|
||||
### Compile-Time Validation
|
||||
|
||||
The DSL validates class and parameter existence during compilation, catching errors before execution:
|
||||
|
||||
- **Factory Functions**: Verifies that animation and color provider factories exist in the animation module
|
||||
- **Parameter Names**: Checks that all named parameters are valid for the specific class
|
||||
- **Parameter Constraints**: Validates parameter values against defined constraints (min/max, enums, types)
|
||||
- **Nested Validation**: Validates parameters in nested function calls and value providers
|
||||
|
||||
### Common Errors
|
||||
|
||||
```dsl
|
||||
# Invalid: Redefining predefined color
|
||||
color red = 0x800000 # Error: Cannot redefine 'red'
|
||||
|
||||
# Invalid: Unknown parameter
|
||||
animation bad = pulsating_animation(invalid_param=123) # Error: Unknown parameter
|
||||
|
||||
# Invalid: Undefined reference
|
||||
animation ref = solid(color=undefined_color) # Error: Undefined reference
|
||||
|
||||
# Valid alternatives
|
||||
color my_red = 0x800000 # OK: Different name
|
||||
animation good = pulsating_animation(color=red, period=2s) # OK: Valid parameters
|
||||
```
|
||||
|
||||
## Formal Grammar (EBNF)
|
||||
|
||||
```ebnf
|
||||
(* Animation DSL Grammar *)
|
||||
|
||||
program = { statement } ;
|
||||
|
||||
statement = config_stmt
|
||||
| definition
|
||||
| property_assignment
|
||||
| sequence
|
||||
| execution_stmt ;
|
||||
|
||||
(* Configuration *)
|
||||
config_stmt = strip_config | variable_assignment ;
|
||||
strip_config = "strip" "length" number ;
|
||||
variable_assignment = "set" identifier "=" expression ;
|
||||
|
||||
(* Definitions *)
|
||||
definition = color_def | palette_def | animation_def ;
|
||||
color_def = "color" identifier "=" color_expression ;
|
||||
palette_def = "palette" identifier "=" palette_array ;
|
||||
animation_def = "animation" identifier "=" animation_expression ;
|
||||
|
||||
(* Property Assignments *)
|
||||
property_assignment = identifier "." identifier "=" expression ;
|
||||
|
||||
(* Sequences *)
|
||||
sequence = "sequence" identifier "{" sequence_body "}" ;
|
||||
sequence_body = { sequence_statement } ;
|
||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt ;
|
||||
|
||||
play_stmt = "play" identifier [ "for" time_expression ] ;
|
||||
wait_stmt = "wait" time_expression ;
|
||||
repeat_stmt = "repeat" number "times" ":" sequence_body ;
|
||||
|
||||
(* Execution *)
|
||||
execution_stmt = "run" identifier ;
|
||||
|
||||
(* Expressions *)
|
||||
expression = logical_or_expr ;
|
||||
logical_or_expr = logical_and_expr { "||" logical_and_expr } ;
|
||||
logical_and_expr = equality_expr { "&&" equality_expr } ;
|
||||
equality_expr = relational_expr { ( "==" | "!=" ) relational_expr } ;
|
||||
relational_expr = additive_expr { ( "<" | "<=" | ">" | ">=" ) additive_expr } ;
|
||||
additive_expr = multiplicative_expr { ( "+" | "-" ) multiplicative_expr } ;
|
||||
multiplicative_expr = unary_expr { ( "*" | "/" | "%" ) unary_expr } ;
|
||||
unary_expr = ( "!" | "-" | "+" ) unary_expr | primary_expr ;
|
||||
primary_expr = literal | identifier | function_call | "(" expression ")" ;
|
||||
|
||||
(* Color Expressions *)
|
||||
color_expression = hex_color | named_color | identifier ;
|
||||
hex_color = "0x" hex_digit{6} | "0x" hex_digit{8} ;
|
||||
named_color = color_name ;
|
||||
|
||||
(* Animation Expressions *)
|
||||
animation_expression = function_call | identifier ;
|
||||
|
||||
(* Palette Arrays *)
|
||||
palette_array = "[" palette_entry { "," palette_entry } "]" ;
|
||||
palette_entry = "(" number "," color_expression ")" ;
|
||||
|
||||
(* Function Calls *)
|
||||
function_call = identifier "(" [ named_argument_list ] ")" ;
|
||||
named_argument_list = named_argument { "," named_argument } ;
|
||||
named_argument = identifier "=" expression ;
|
||||
|
||||
(* Time Expressions *)
|
||||
time_expression = time_literal ;
|
||||
time_literal = number time_unit ;
|
||||
time_unit = "ms" | "s" | "m" | "h" ;
|
||||
|
||||
(* Literals *)
|
||||
literal = number | string | color_expression | time_expression | percentage | boolean ;
|
||||
number = integer | real ;
|
||||
integer = [ "-" ] digit { digit } ;
|
||||
real = [ "-" ] digit { digit } "." digit { digit } ;
|
||||
string = '"' { string_char } '"' | "'" { string_char } "'" ;
|
||||
percentage = number "%" ;
|
||||
boolean = "true" | "false" ;
|
||||
|
||||
(* Identifiers *)
|
||||
identifier = ( letter | "_" ) { letter | digit | "_" } ;
|
||||
color_name = "red" | "green" | "blue" | "white" | "black" | "yellow"
|
||||
| "orange" | "purple" | "pink" | "cyan" | "magenta" | "gray"
|
||||
| "silver" | "gold" | "brown" | "lime" | "navy" | "olive"
|
||||
| "maroon" | "teal" | "aqua" | "fuchsia" | "transparent" ;
|
||||
|
||||
(* Character Classes *)
|
||||
letter = "a" .. "z" | "A" .. "Z" ;
|
||||
digit = "0" .. "9" ;
|
||||
hex_digit = digit | "A" .. "F" | "a" .. "f" ;
|
||||
string_char = (* any character except quote *) ;
|
||||
|
||||
(* Comments and Whitespace *)
|
||||
comment = "#" { (* any character except newline *) } newline ;
|
||||
whitespace = " " | "\t" | "\r" | "\n" ;
|
||||
newline = "\n" | "\r\n" ;
|
||||
```
|
||||
|
||||
## Language Features Summary
|
||||
|
||||
### ✅ Currently Implemented
|
||||
- Comments with preservation
|
||||
- Strip configuration (optional)
|
||||
- Color definitions (hex and named)
|
||||
- Palette definitions with VRGB conversion
|
||||
- Animation definitions with named parameters
|
||||
- Property assignments
|
||||
- Basic sequences (play, wait, repeat)
|
||||
- Variable assignments with type conversion
|
||||
- Reserved name validation
|
||||
- Parameter validation at compile time
|
||||
- Execution statements
|
||||
- User-defined functions (with engine-first parameter pattern) - see **[User Functions Guide](USER_FUNCTIONS.md)**
|
||||
|
||||
### 🚧 Partially Implemented
|
||||
- Expression evaluation (basic support)
|
||||
- Nested function calls (working but limited)
|
||||
- Error recovery (basic error reporting)
|
||||
|
||||
### ❌ Planned Features
|
||||
- Advanced control flow (if/else, choose random)
|
||||
- Event system and handlers
|
||||
- Mathematical expressions
|
||||
- Variable references with $ syntax
|
||||
- Spatial operations and zones
|
||||
- 2D matrix support
|
||||
|
||||
This reference provides the complete syntax specification for the Animation DSL language as currently implemented and planned for future development.
|
||||
434
lib/libesp32/berry_animation/docs/DSL_TRANSPILATION.md
Normal file
434
lib/libesp32/berry_animation/docs/DSL_TRANSPILATION.md
Normal file
@ -0,0 +1,434 @@
|
||||
# DSL Reference - Berry Animation Framework
|
||||
|
||||
This document provides a comprehensive reference for the Animation DSL (Domain-Specific Language), which allows you to define animations using a declarative syntax with named parameters.
|
||||
|
||||
## Module Import
|
||||
|
||||
The DSL functionality is provided by a separate module:
|
||||
|
||||
```berry
|
||||
import animation # Core framework (required)
|
||||
import animation_dsl # DSL compiler and runtime (required for DSL)
|
||||
```
|
||||
|
||||
## Why Use the DSL?
|
||||
|
||||
### Benefits
|
||||
- **Declarative syntax**: Describe what you want, not how to implement it
|
||||
- **Readable code**: Natural language-like syntax
|
||||
- **Rapid prototyping**: Quick iteration on animation ideas
|
||||
- **Event-driven**: Built-in support for interactive animations
|
||||
- **Composition**: Easy layering and sequencing of animations
|
||||
|
||||
### When to Use DSL vs Programmatic
|
||||
|
||||
**Use DSL when:**
|
||||
- Creating complex animation sequences
|
||||
- Building interactive, event-driven animations
|
||||
- Rapid prototyping and experimentation
|
||||
- Non-programmers need to create animations
|
||||
- You want declarative, readable animation definitions
|
||||
|
||||
**Use programmatic API when:**
|
||||
- Building reusable animation components
|
||||
- Performance is critical (DSL has compilation overhead)
|
||||
- You need fine-grained control over animation logic
|
||||
- Integrating with existing Berry code
|
||||
- Firmware size is constrained (DSL module can be excluded)
|
||||
|
||||
## DSL API Functions
|
||||
|
||||
### Core Functions
|
||||
|
||||
#### `animation_dsl.compile(source)`
|
||||
Compiles DSL source code to Berry code without executing it.
|
||||
|
||||
```berry
|
||||
var dsl_source = "color red = #FF0000\n"
|
||||
"animation red_anim = solid(color=red)\n"
|
||||
"run red_anim"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
print(berry_code) # Shows generated Berry code
|
||||
```
|
||||
|
||||
#### `animation_dsl.execute(source)`
|
||||
Compiles and executes DSL source code in one step.
|
||||
|
||||
```berry
|
||||
animation_dsl.execute("color blue = #0000FF\n"
|
||||
"animation blue_anim = solid(color=blue)\n"
|
||||
"run blue_anim for 5s")
|
||||
```
|
||||
|
||||
#### `animation_dsl.load_file(filename)`
|
||||
Loads DSL source from a file and executes it.
|
||||
|
||||
```berry
|
||||
# Create a DSL file
|
||||
var f = open("my_animation.dsl", "w")
|
||||
f.write("color green = #00FF00\n"
|
||||
"animation pulse_green = pulsating_animation(color=green, period=2s)\n"
|
||||
"run pulse_green")
|
||||
f.close()
|
||||
|
||||
# Load and execute
|
||||
animation_dsl.load_file("my_animation.dsl")
|
||||
```
|
||||
|
||||
### Runtime Management
|
||||
|
||||
#### `animation_dsl.create_runtime()`
|
||||
Creates a DSL runtime instance for advanced control.
|
||||
|
||||
```berry
|
||||
var runtime = animation_dsl.create_runtime()
|
||||
runtime.load_dsl(dsl_source)
|
||||
runtime.execute()
|
||||
```
|
||||
|
||||
## DSL Language Overview
|
||||
|
||||
The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility.
|
||||
|
||||
### Key Syntax Features
|
||||
|
||||
- **Named parameters**: All function calls use `name=value` syntax
|
||||
- **Time units**: `2s`, `500ms`, `1m`, `1h`
|
||||
- **Hex colors**: `#FF0000`, `#80FF0000` (ARGB)
|
||||
- **Named colors**: `red`, `blue`, `white`, etc.
|
||||
- **Comments**: `# This is a comment`
|
||||
- **Property assignment**: `animation.property = value`
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```dsl
|
||||
# Optional strip configuration
|
||||
strip length 60
|
||||
|
||||
# Color definitions
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Animation definitions with named parameters
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
|
||||
|
||||
# Property assignments
|
||||
pulse_red.priority = 10
|
||||
comet_blue.direction = -1
|
||||
|
||||
# Execution
|
||||
run pulse_red
|
||||
|
||||
```
|
||||
|
||||
The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually.
|
||||
## Advanced DSL Features
|
||||
|
||||
### User-Defined Functions
|
||||
|
||||
Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments:
|
||||
|
||||
```berry
|
||||
# Define custom function in Berry - engine must be first parameter
|
||||
def custom_sparkle(engine, color, density, speed)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
return anim
|
||||
end
|
||||
|
||||
# Register the function for DSL use
|
||||
animation.register_user_function("sparkle", custom_sparkle)
|
||||
```
|
||||
|
||||
```dsl
|
||||
# Use in DSL - engine is automatically passed as first argument
|
||||
animation gold_sparkle = sparkle(#FFD700, 8, 500ms)
|
||||
animation blue_sparkle = sparkle(blue, 12, 300ms)
|
||||
run gold_sparkle
|
||||
```
|
||||
|
||||
**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function.
|
||||
|
||||
For comprehensive examples and best practices, see the **[User Functions Guide](USER_FUNCTIONS.md)**.
|
||||
|
||||
### Event System
|
||||
|
||||
Define event handlers that respond to triggers:
|
||||
|
||||
```dsl
|
||||
# Define animations for different states
|
||||
color normal = #000080
|
||||
color alert = #FF0000
|
||||
|
||||
animation normal_state = solid(color=normal)
|
||||
animation alert_state = pulsating_animation(color=alert, period=500ms)
|
||||
|
||||
# Event handlers
|
||||
on button_press {
|
||||
run alert_state for 3s
|
||||
run normal_state
|
||||
}
|
||||
|
||||
on sensor_trigger {
|
||||
run alert_state for 5s
|
||||
wait 1s
|
||||
run normal_state
|
||||
}
|
||||
|
||||
# Default state
|
||||
run normal_state
|
||||
```
|
||||
|
||||
### Nested Function Calls
|
||||
|
||||
DSL supports nested function calls for complex compositions:
|
||||
|
||||
```dsl
|
||||
# Nested calls in animation definitions (now supported)
|
||||
animation complex = pulsating_animation(
|
||||
source=shift_animation(
|
||||
source=solid(color=red),
|
||||
offset=triangle(min=0, max=29, period=3s)
|
||||
),
|
||||
period=2s
|
||||
)
|
||||
|
||||
# Nested calls in run statements
|
||||
sequence demo {
|
||||
play pulsating_animation(source=shift_animation(source=solid(color=blue), offset=5), period=1s) for 10s
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The DSL compiler validates classes and parameters at transpilation time, catching errors before execution:
|
||||
|
||||
```berry
|
||||
var invalid_dsl = "color red = #INVALID_COLOR\n"
|
||||
"animation bad = unknown_function(red)\n"
|
||||
"animation pulse = pulsating_animation(invalid_param=123)"
|
||||
|
||||
try
|
||||
animation_dsl.execute(invalid_dsl)
|
||||
except .. as e
|
||||
print("DSL Error:", e)
|
||||
end
|
||||
```
|
||||
|
||||
### Transpilation-Time Validation
|
||||
|
||||
The DSL performs comprehensive validation during compilation:
|
||||
|
||||
**Animation Factory Validation:**
|
||||
```dsl
|
||||
# Error: Function doesn't exist
|
||||
animation bad = nonexistent_animation(color=red)
|
||||
# Transpiler error: "Animation factory function 'nonexistent_animation' does not exist"
|
||||
|
||||
# Error: Function exists but doesn't create animation
|
||||
animation bad2 = math_function(value=10)
|
||||
# Transpiler error: "Function 'math_function' does not create an animation instance"
|
||||
```
|
||||
|
||||
**Parameter Validation:**
|
||||
```dsl
|
||||
# Error: Invalid parameter name
|
||||
animation pulse = pulsating_animation(invalid_param=123)
|
||||
# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation"
|
||||
|
||||
# Error: Parameter constraint violation
|
||||
animation comet = comet_animation(tail_length=-5)
|
||||
# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1"
|
||||
```
|
||||
|
||||
**Color Provider Validation:**
|
||||
```dsl
|
||||
# Error: Color provider doesn't exist
|
||||
color bad = nonexistent_color_provider(period=2s)
|
||||
# Transpiler error: "Color provider factory 'nonexistent_color_provider' does not exist"
|
||||
|
||||
# Error: Function exists but doesn't create color provider
|
||||
color bad2 = pulsating_animation(color=red)
|
||||
# Transpiler error: "Function 'pulsating_animation' does not create a color provider instance"
|
||||
```
|
||||
|
||||
**Reference Validation:**
|
||||
```dsl
|
||||
# Error: Undefined color reference
|
||||
animation pulse = pulsating_animation(color=undefined_color)
|
||||
# Transpiler error: "Undefined reference: 'undefined_color'"
|
||||
|
||||
# Error: Undefined animation reference
|
||||
run nonexistent_animation
|
||||
# Transpiler error: "Undefined reference: 'nonexistent_animation'"
|
||||
```
|
||||
|
||||
### Error Categories
|
||||
|
||||
- **Syntax errors**: Invalid DSL syntax (lexer/parser errors)
|
||||
- **Factory validation**: Non-existent or invalid animation/color provider factories
|
||||
- **Parameter validation**: Invalid parameter names or constraint violations
|
||||
- **Reference validation**: Using undefined colors, animations, or variables
|
||||
- **Type validation**: Incorrect parameter types or incompatible assignments
|
||||
- **Runtime errors**: Errors during Berry code execution (rare with good validation)
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### DSL vs Programmatic Performance
|
||||
|
||||
- **DSL compilation overhead**: ~10-50ms depending on complexity
|
||||
- **Generated code performance**: Identical to hand-written Berry code
|
||||
- **Memory usage**: DSL compiler uses temporary memory during compilation
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Compile once, run many times**:
|
||||
```berry
|
||||
var compiled = animation_dsl.compile(dsl_source)
|
||||
var fn = compile(compiled)
|
||||
|
||||
# Run multiple times without recompilation
|
||||
fn() # First execution
|
||||
fn() # Subsequent executions are faster
|
||||
```
|
||||
|
||||
2. **Use programmatic API for performance-critical code**:
|
||||
```berry
|
||||
# DSL for high-level structure
|
||||
animation_dsl.execute('''
|
||||
sequence main {
|
||||
play performance_critical_anim for 10s
|
||||
}
|
||||
run main
|
||||
''')
|
||||
|
||||
# Programmatic for performance-critical animations
|
||||
var performance_critical_anim = animation.create_optimized_animation()
|
||||
```
|
||||
|
||||
3. **Minimize DSL recompilation**:
|
||||
```berry
|
||||
# Good: Compile once
|
||||
var runtime = animation_dsl.create_runtime()
|
||||
runtime.load_dsl(source)
|
||||
runtime.execute()
|
||||
|
||||
# Avoid: Recompiling same DSL repeatedly
|
||||
# animation_dsl.execute(same_source) # Don't do this in loops
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### With Tasmota Rules
|
||||
|
||||
```berry
|
||||
# In autoexec.be
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
def handle_rule_trigger(event)
|
||||
if event == "motion"
|
||||
animation_dsl.execute("color alert = #FF0000\n"
|
||||
"animation alert_anim = pulsating_animation(color=alert, period=500ms)\n"
|
||||
"run alert_anim for 5s")
|
||||
elif event == "door"
|
||||
animation_dsl.execute("color welcome = #00FF00\n"
|
||||
"animation welcome_anim = breathe_animation(color=welcome, period=2s)\n"
|
||||
"run welcome_anim for 8s")
|
||||
end
|
||||
end
|
||||
|
||||
# Register with Tasmota's rule system
|
||||
tasmota.add_rule("motion", handle_rule_trigger)
|
||||
```
|
||||
|
||||
### With Web Interface
|
||||
|
||||
```berry
|
||||
# Create web endpoints for DSL execution
|
||||
import webserver
|
||||
|
||||
def web_execute_dsl()
|
||||
var dsl_code = webserver.arg("dsl")
|
||||
if dsl_code
|
||||
try
|
||||
animation_dsl.execute(dsl_code)
|
||||
webserver.content_response("DSL executed successfully")
|
||||
except .. as e
|
||||
webserver.content_response(f"DSL Error: {e}")
|
||||
end
|
||||
else
|
||||
webserver.content_response("No DSL code provided")
|
||||
end
|
||||
end
|
||||
|
||||
webserver.on("/execute_dsl", web_execute_dsl)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Structure your DSL files**:
|
||||
```dsl
|
||||
# Strip configuration first
|
||||
strip length 60
|
||||
|
||||
# Colors next
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Animations with named parameters
|
||||
animation red_solid = solid(color=red)
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
|
||||
# Property assignments
|
||||
pulse_red.priority = 10
|
||||
|
||||
# Sequences
|
||||
sequence demo {
|
||||
play pulse_red for 5s
|
||||
}
|
||||
|
||||
# Execution last
|
||||
run demo
|
||||
```
|
||||
|
||||
2. **Use meaningful names**:
|
||||
```dsl
|
||||
# Good
|
||||
color warning_red = #FF0000
|
||||
animation door_alert = pulsating_animation(color=warning_red, period=500ms)
|
||||
|
||||
# Avoid
|
||||
color c1 = #FF0000
|
||||
animation a1 = pulsating_animation(color=c1, period=500ms)
|
||||
```
|
||||
|
||||
3. **Comment your DSL**:
|
||||
```dsl
|
||||
# Security system colors
|
||||
color normal_blue = #000080 # Idle state
|
||||
color alert_red = #FF0000 # Alert state
|
||||
color success_green = #00FF00 # Success state
|
||||
|
||||
# Main security animation sequence
|
||||
sequence security_demo {
|
||||
play solid(color=normal_blue) for 10s # Normal operation
|
||||
play pulsating_animation(color=alert_red, period=500ms) for 3s # Alert
|
||||
play breathe_animation(color=success_green, period=2s) for 5s # Success confirmation
|
||||
}
|
||||
```
|
||||
|
||||
4. **Organize complex projects**:
|
||||
```berry
|
||||
# Load DSL modules
|
||||
animation_dsl.load_file("colors.dsl") # Color definitions
|
||||
animation_dsl.load_file("animations.dsl") # Animation library
|
||||
animation_dsl.load_file("sequences.dsl") # Sequence definitions
|
||||
animation_dsl.load_file("main.dsl") # Main execution
|
||||
```
|
||||
|
||||
This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed.
|
||||
@ -1,568 +1,176 @@
|
||||
# Examples
|
||||
|
||||
Curated examples showcasing the Tasmota Berry Animation Framework capabilities.
|
||||
Essential examples showcasing the Tasmota Berry Animation Framework using DSL syntax.
|
||||
|
||||
## Basic Examples
|
||||
## Basic Animations
|
||||
|
||||
### 1. Simple Solid Color
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create solid red animation
|
||||
var red = animation.solid(0xFFFF0000)
|
||||
engine.add_animation(red).start()
|
||||
```
|
||||
|
||||
**DSL Version:**
|
||||
### 1. Solid Color
|
||||
```dsl
|
||||
color red = #FF0000
|
||||
animation solid_red = solid(red)
|
||||
run solid_red
|
||||
color red = 0xFF0000
|
||||
animation red_solid = solid(color=red)
|
||||
run red_solid
|
||||
```
|
||||
|
||||
### 2. Pulsing Effect
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create pulsing blue animation
|
||||
var pulse_blue = animation.pulse(
|
||||
animation.solid(0xFF0000FF), # Blue color
|
||||
3000, # 3 second period
|
||||
50, # Min brightness
|
||||
255 # Max brightness
|
||||
)
|
||||
|
||||
engine.add_animation(pulse_blue).start()
|
||||
```dsl
|
||||
color blue = 0x0000FF
|
||||
animation blue_pulse = pulsating_animation(color=blue, period=2s)
|
||||
run blue_pulse
|
||||
```
|
||||
|
||||
**DSL Version:**
|
||||
### 3. Moving Comet
|
||||
```dsl
|
||||
color blue = #0000FF
|
||||
animation pulse_blue = pulse(solid(blue), 3s, 20%, 100%)
|
||||
run pulse_blue
|
||||
color cyan = 0x00FFFF
|
||||
animation comet_trail = comet_animation(color=cyan, tail_length=8, speed=100ms, direction=1)
|
||||
run comet_trail
|
||||
```
|
||||
|
||||
### 3. Breathing Effect
|
||||
## Using Value Providers
|
||||
|
||||
**DSL:**
|
||||
### 4. Breathing Effect
|
||||
```dsl
|
||||
color soft_white = #C0C0C0
|
||||
animation breathing = breathe(soft_white, 4s)
|
||||
run breathing
|
||||
set breathing = smooth(min_value=50, max_value=255, period=3s)
|
||||
color white = 0xFFFFFF
|
||||
animation breathing_white = solid(color=white)
|
||||
breathing_white.opacity = breathing
|
||||
run breathing_white
|
||||
```
|
||||
|
||||
## Color and Palette Examples
|
||||
|
||||
### 4. Fire Effect
|
||||
|
||||
**DSL:**
|
||||
### 5. Color Cycling
|
||||
```dsl
|
||||
# Define fire palette
|
||||
palette fire_colors = [
|
||||
(0, #000000), # Black
|
||||
(64, #800000), # Dark red
|
||||
(128, #FF0000), # Red
|
||||
(192, #FF8000), # Orange
|
||||
(255, #FFFF00) # Yellow
|
||||
]
|
||||
|
||||
# Create fire animation
|
||||
animation fire_effect = rich_palette_animation(fire_colors, 2s, smooth, 255)
|
||||
run fire_effect
|
||||
```
|
||||
|
||||
### 5. Rainbow Cycle
|
||||
|
||||
**DSL:**
|
||||
```dsl
|
||||
palette rainbow = [
|
||||
(0, red), (42, orange), (84, yellow),
|
||||
(126, green), (168, blue), (210, indigo), (255, violet)
|
||||
]
|
||||
|
||||
animation rainbow_cycle = rich_palette_animation(rainbow, 8s, smooth, 255)
|
||||
color rainbow = rainbow_color_provider(period=5s)
|
||||
animation rainbow_cycle = solid(color=rainbow)
|
||||
run rainbow_cycle
|
||||
```
|
||||
|
||||
### 6. Ocean Waves
|
||||
## Palette Animations
|
||||
|
||||
**DSL:**
|
||||
### 6. Fire Effect
|
||||
```dsl
|
||||
palette ocean = [
|
||||
(0, navy), # Deep ocean
|
||||
(64, blue), # Ocean blue
|
||||
(128, cyan), # Shallow water
|
||||
(192, #87CEEB), # Sky blue
|
||||
(255, white) # Foam
|
||||
palette fire_colors = [
|
||||
(0, 0x000000), # Black
|
||||
(128, 0xFF0000), # Red
|
||||
(192, 0xFF8000), # Orange
|
||||
(255, 0xFFFF00) # Yellow
|
||||
]
|
||||
|
||||
animation ocean_waves = rich_palette_animation(ocean, 6s, smooth, 200)
|
||||
run ocean_waves
|
||||
animation fire_effect = palette_animation(palette=fire_colors, period=2s, intensity=255)
|
||||
run fire_effect
|
||||
```
|
||||
|
||||
## Position-Based Examples
|
||||
## Sequences
|
||||
|
||||
### 7. Center Pulse
|
||||
|
||||
**DSL:**
|
||||
### 7. RGB Show
|
||||
```dsl
|
||||
strip length 60
|
||||
color white = #FFFFFF
|
||||
color red = 0xFF0000
|
||||
color green = 0x00FF00
|
||||
color blue = 0x0000FF
|
||||
|
||||
# Pulse at center position
|
||||
animation center_pulse = pulse_position_animation(white, 30, 5, 3)
|
||||
run center_pulse
|
||||
```
|
||||
animation red_anim = solid(color=red)
|
||||
animation green_anim = solid(color=green)
|
||||
animation blue_anim = solid(color=blue)
|
||||
|
||||
### 8. Moving Comet
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create cyan comet with 8-pixel tail
|
||||
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
|
||||
engine.add_animation(comet).start()
|
||||
```
|
||||
|
||||
### 9. Twinkling Stars
|
||||
|
||||
**DSL:**
|
||||
```dsl
|
||||
color star_white = #FFFFFF
|
||||
animation stars = twinkle_animation(star_white, 8, 500ms)
|
||||
run stars
|
||||
```
|
||||
|
||||
## Dynamic Parameter Examples
|
||||
|
||||
### 10. Moving Pulse
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create dynamic position that moves back and forth
|
||||
var moving_pos = animation.smooth(5, 55, 4000) # 4-second cycle
|
||||
|
||||
# Create pulse with dynamic position
|
||||
var moving_pulse = animation.pulse_position_animation(
|
||||
0xFFFF0000, # Red color
|
||||
moving_pos, # Dynamic position
|
||||
3, # Pulse size
|
||||
2 # Fade size
|
||||
)
|
||||
|
||||
engine.add_animation(moving_pulse).start()
|
||||
```
|
||||
|
||||
### 11. Color-Changing Pulse
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create color cycle provider
|
||||
var color_cycle = animation.color_cycle_color_provider(
|
||||
[0xFFFF0000, 0xFF00FF00, 0xFF0000FF], # Red, Green, Blue
|
||||
5000, # 5-second cycle
|
||||
1 # Smooth transitions
|
||||
)
|
||||
|
||||
# Create filled animation with dynamic color
|
||||
var color_changing = animation.filled(color_cycle, 0, 0, true, "color_cycle")
|
||||
engine.add_animation(color_changing).start()
|
||||
```
|
||||
|
||||
### 12. Breathing Size
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Dynamic pulse size that breathes
|
||||
var breathing_size = animation.smooth(1, 10, 3000)
|
||||
|
||||
# Pulse with breathing size
|
||||
var breathing_pulse = animation.pulse_position_animation(
|
||||
0xFF8000FF, # Purple color
|
||||
30, # Center position
|
||||
breathing_size, # Dynamic size
|
||||
1 # Fade size
|
||||
)
|
||||
|
||||
engine.add_animation(breathing_pulse).start()
|
||||
```
|
||||
|
||||
## Sequence Examples
|
||||
|
||||
### 13. RGB Show
|
||||
|
||||
**DSL:**
|
||||
```dsl
|
||||
# Define colors
|
||||
color red = #FF0000
|
||||
color green = #00FF00
|
||||
color blue = #0000FF
|
||||
|
||||
# Create animations
|
||||
animation red_pulse = pulse(solid(red), 2s, 50%, 100%)
|
||||
animation green_pulse = pulse(solid(green), 2s, 50%, 100%)
|
||||
animation blue_pulse = pulse(solid(blue), 2s, 50%, 100%)
|
||||
|
||||
# Create sequence
|
||||
sequence rgb_show {
|
||||
play red_pulse for 3s
|
||||
wait 500ms
|
||||
play green_pulse for 3s
|
||||
wait 500ms
|
||||
play blue_pulse for 3s
|
||||
wait 1s
|
||||
repeat 3 times:
|
||||
play red_pulse for 1s
|
||||
play green_pulse for 1s
|
||||
play blue_pulse for 1s
|
||||
play red_anim for 2s
|
||||
play green_anim for 2s
|
||||
play blue_anim for 2s
|
||||
}
|
||||
|
||||
run rgb_show
|
||||
```
|
||||
|
||||
### 14. Sunrise Sequence
|
||||
|
||||
**DSL:**
|
||||
### 8. Sunrise Sequence
|
||||
```dsl
|
||||
# Define sunrise colors
|
||||
color deep_blue = #000080
|
||||
color purple = #800080
|
||||
color pink = #FF69B4
|
||||
color orange = #FFA500
|
||||
color yellow = #FFFF00
|
||||
color deep_blue = 0x000080
|
||||
color orange = 0xFFA500
|
||||
color yellow = 0xFFFF00
|
||||
|
||||
# Create animations
|
||||
animation night = solid(deep_blue)
|
||||
animation dawn = pulse(solid(purple), 4s, 30%, 100%)
|
||||
animation sunrise = pulse(solid(pink), 3s, 50%, 100%)
|
||||
animation morning = pulse(solid(orange), 2s, 70%, 100%)
|
||||
animation day = solid(yellow)
|
||||
animation night = solid(color=deep_blue)
|
||||
animation sunrise = pulsating_animation(color=orange, period=3s)
|
||||
animation day = solid(color=yellow)
|
||||
|
||||
# Sunrise sequence
|
||||
sequence sunrise_show {
|
||||
play night for 2s
|
||||
play dawn for 8s
|
||||
play sunrise for 6s
|
||||
play morning for 4s
|
||||
play day for 5s
|
||||
play night for 3s
|
||||
play sunrise for 5s
|
||||
play day for 3s
|
||||
}
|
||||
|
||||
run sunrise_show
|
||||
```
|
||||
|
||||
### 15. Party Mode
|
||||
|
||||
**DSL:**
|
||||
```dsl
|
||||
# Party colors
|
||||
color hot_pink = #FF1493
|
||||
color lime = #00FF00
|
||||
color cyan = #00FFFF
|
||||
color magenta = #FF00FF
|
||||
|
||||
# Fast animations
|
||||
animation pink_flash = pulse(solid(hot_pink), 500ms, 80%, 100%)
|
||||
animation lime_flash = pulse(solid(lime), 600ms, 80%, 100%)
|
||||
animation cyan_flash = pulse(solid(cyan), 400ms, 80%, 100%)
|
||||
animation magenta_flash = pulse(solid(magenta), 700ms, 80%, 100%)
|
||||
|
||||
# Party sequence
|
||||
sequence party_mode {
|
||||
repeat 10 times:
|
||||
play pink_flash for 1s
|
||||
play lime_flash for 1s
|
||||
play cyan_flash for 800ms
|
||||
play magenta_flash for 1200ms
|
||||
}
|
||||
|
||||
run party_mode
|
||||
```
|
||||
|
||||
## Interactive Examples
|
||||
|
||||
### 16. Button-Controlled Colors
|
||||
|
||||
**DSL:**
|
||||
```dsl
|
||||
# Define colors
|
||||
color red = #FF0000
|
||||
color green = #00FF00
|
||||
color blue = #0000FF
|
||||
color white = #FFFFFF
|
||||
|
||||
# Define animations
|
||||
animation red_glow = solid(red)
|
||||
animation green_glow = solid(green)
|
||||
animation blue_glow = solid(blue)
|
||||
animation white_flash = pulse(solid(white), 500ms, 50%, 100%)
|
||||
|
||||
# Event handlers
|
||||
on button_press: white_flash
|
||||
on timer(5s): red_glow
|
||||
on timer(10s): green_glow
|
||||
on timer(15s): blue_glow
|
||||
|
||||
# Default animation
|
||||
run red_glow
|
||||
```
|
||||
|
||||
### 17. Brightness-Responsive Animation
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Brightness-responsive handler
|
||||
def brightness_handler(event_data)
|
||||
var brightness = event_data.find("brightness", 128)
|
||||
|
||||
if brightness > 200
|
||||
# Bright environment - subtle colors
|
||||
var subtle = animation.solid(0xFF404040) # Dim white
|
||||
engine.clear()
|
||||
engine.add_animation(subtle)
|
||||
elif brightness > 100
|
||||
# Medium light - normal colors
|
||||
var normal = animation.pulse(animation.solid(0xFF0080FF), 3000, 100, 255)
|
||||
engine.clear()
|
||||
engine.add_animation(normal)
|
||||
else
|
||||
# Dark environment - bright colors
|
||||
var bright = animation.pulse(animation.solid(0xFFFFFFFF), 2000, 200, 255)
|
||||
engine.clear()
|
||||
engine.add_animation(bright)
|
||||
end
|
||||
end
|
||||
|
||||
# Register brightness handler
|
||||
animation.register_event_handler("brightness_change", brightness_handler, 5)
|
||||
|
||||
# Start with default animation
|
||||
var default_anim = animation.pulse(animation.solid(0xFF8080FF), 3000, 100, 255)
|
||||
engine.add_animation(default_anim).start()
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### 18. Aurora Borealis
|
||||
|
||||
**DSL:**
|
||||
### 9. Dynamic Position
|
||||
```dsl
|
||||
strip length 60
|
||||
|
||||
# Aurora palette with ethereal colors
|
||||
palette aurora = [
|
||||
(0, #000022), # Dark night sky
|
||||
(32, #001144), # Deep blue
|
||||
(64, #004400), # Dark green
|
||||
(96, #006633), # Forest green
|
||||
(128, #00AA44), # Aurora green
|
||||
(160, #44AA88), # Light green
|
||||
(192, #66CCAA), # Pale green
|
||||
(224, #88FFCC), # Bright aurora
|
||||
(255, #AAFFDD) # Ethereal glow
|
||||
]
|
||||
set moving_position = smooth(min_value=5, max_value=55, period=4s)
|
||||
color purple = 0x8000FF
|
||||
|
||||
# Slow, ethereal aurora animation
|
||||
animation aurora_borealis = rich_palette_animation(aurora, 12s, smooth, 180)
|
||||
|
||||
# Set properties for mystical effect
|
||||
aurora_borealis.priority = 10
|
||||
aurora_borealis.opacity = 220
|
||||
|
||||
run aurora_borealis
|
||||
animation moving_pulse = beacon_animation(
|
||||
color=purple,
|
||||
position=moving_position,
|
||||
beacon_size=3,
|
||||
fade_size=2
|
||||
)
|
||||
run moving_pulse
|
||||
```
|
||||
|
||||
### 19. Campfire Simulation
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(40)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create fire animation with realistic parameters
|
||||
var fire = animation.fire_animation(180, 120) # Medium intensity, moderate speed
|
||||
|
||||
# Add some twinkling embers
|
||||
var embers = animation.twinkle_animation(0xFFFF4500, 3, 800) # Orange embers
|
||||
embers.set_priority(5) # Lower priority than fire
|
||||
embers.set_opacity(150) # Semi-transparent
|
||||
|
||||
# Combine fire and embers
|
||||
engine.add_animation(fire)
|
||||
engine.add_animation(embers)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### 20. User-Defined Function Example
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
# Define custom breathing effect
|
||||
def custom_breathing(base_color, period, min_percent, max_percent)
|
||||
var min_brightness = int(tasmota.scale_uint(min_percent, 0, 100, 0, 255))
|
||||
var max_brightness = int(tasmota.scale_uint(max_percent, 0, 100, 0, 255))
|
||||
|
||||
return animation.pulse(
|
||||
animation.solid(base_color),
|
||||
period,
|
||||
min_brightness,
|
||||
max_brightness
|
||||
)
|
||||
end
|
||||
|
||||
# Register the function
|
||||
animation.register_user_function("breathing", custom_breathing)
|
||||
|
||||
# Now use in DSL
|
||||
var dsl_code = '''
|
||||
color soft_blue = #4080FF
|
||||
animation calm_breathing = breathing(soft_blue, 4000, 10, 90)
|
||||
run calm_breathing
|
||||
'''
|
||||
|
||||
var strip = Leds(30)
|
||||
var runtime = animation.DSLRuntime(animation.create_engine(strip))
|
||||
runtime.load_dsl(dsl_code)
|
||||
```
|
||||
|
||||
## Performance Examples
|
||||
|
||||
### 21. Efficient Multi-Animation
|
||||
|
||||
**Berry Code:**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create shared value providers for efficiency
|
||||
var slow_breathing = animation.smooth(100, 255, 4000)
|
||||
var position_sweep = animation.linear(5, 55, 6000)
|
||||
|
||||
# Create multiple animations using shared providers
|
||||
var pulse1 = animation.pulse_position_animation(0xFFFF0000, 15, 3, 1)
|
||||
pulse1.set_opacity(slow_breathing) # Shared breathing effect
|
||||
|
||||
var pulse2 = animation.pulse_position_animation(0xFF00FF00, 30, 3, 1)
|
||||
pulse2.set_opacity(slow_breathing) # Same breathing effect
|
||||
|
||||
var pulse3 = animation.pulse_position_animation(0xFF0000FF, 45, 3, 1)
|
||||
pulse3.set_opacity(slow_breathing) # Same breathing effect
|
||||
|
||||
# Add all animations
|
||||
engine.add_animation(pulse1)
|
||||
engine.add_animation(pulse2)
|
||||
engine.add_animation(pulse3)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### 22. Memory-Efficient Palette Cycling
|
||||
|
||||
**DSL:**
|
||||
### 10. Multi-Layer Effect
|
||||
```dsl
|
||||
# Define single palette for multiple uses
|
||||
palette shared_rainbow = [
|
||||
(0, red), (51, orange), (102, yellow),
|
||||
(153, green), (204, blue), (255, violet)
|
||||
]
|
||||
# Base layer - slow breathing
|
||||
set breathing = smooth(min_value=100, max_value=255, period=4s)
|
||||
color base_blue = 0x000080
|
||||
animation base_layer = solid(color=base_blue)
|
||||
base_layer.opacity = breathing
|
||||
|
||||
# Create multiple animations with different speeds using same palette
|
||||
animation fast_rainbow = rich_palette_animation(shared_rainbow, 3s, smooth, 255)
|
||||
animation slow_rainbow = rich_palette_animation(shared_rainbow, 10s, smooth, 180)
|
||||
# Accent layer - twinkling stars
|
||||
color star_white = 0xFFFFFF
|
||||
animation stars = twinkle_animation(color=star_white, count=5, period=800ms)
|
||||
stars.opacity = 150
|
||||
|
||||
# Use in sequence to avoid simultaneous memory usage
|
||||
sequence efficient_show {
|
||||
play fast_rainbow for 15s
|
||||
wait 1s
|
||||
play slow_rainbow for 20s
|
||||
sequence layered_effect {
|
||||
play base_layer for 10s
|
||||
play stars for 10s
|
||||
}
|
||||
|
||||
run efficient_show
|
||||
run layered_effect
|
||||
```
|
||||
|
||||
## Tips for Creating Your Own Examples
|
||||
## Tips for Creating Animations
|
||||
|
||||
### 1. Start Simple
|
||||
Begin with basic solid colors and simple pulses before adding complexity.
|
||||
|
||||
### 2. Use Meaningful Names
|
||||
### Start Simple
|
||||
```dsl
|
||||
# Good
|
||||
color sunset_orange = #FF8C00
|
||||
animation evening_glow = pulse(solid(sunset_orange), 4s, 30%, 100%)
|
||||
|
||||
# Less clear
|
||||
color c1 = #FF8C00
|
||||
animation a1 = pulse(solid(c1), 4s, 30%, 100%)
|
||||
# Begin with basic colors and effects
|
||||
color my_color = 0xFF0000
|
||||
animation simple = solid(color=my_color)
|
||||
run simple
|
||||
```
|
||||
|
||||
### 3. Comment Your Code
|
||||
### Use Meaningful Names
|
||||
```dsl
|
||||
# Sunrise simulation - starts dark and gradually brightens
|
||||
palette sunrise_colors = [
|
||||
(0, #000033), # Pre-dawn darkness
|
||||
(64, #663366), # Purple twilight
|
||||
(128, #CC6633), # Orange sunrise
|
||||
(255, #FFFF99) # Bright morning
|
||||
]
|
||||
# Good - descriptive names
|
||||
color sunset_orange = 0xFF8C00
|
||||
animation evening_glow = pulsating_animation(color=sunset_orange, period=4s)
|
||||
|
||||
# Avoid - unclear names
|
||||
color c1 = 0xFF8C00
|
||||
animation a1 = pulsating_animation(color=c1, period=4s)
|
||||
```
|
||||
|
||||
### 4. Test Incrementally
|
||||
Build complex animations step by step:
|
||||
1. Test basic colors
|
||||
2. Add simple effects
|
||||
3. Combine with sequences
|
||||
4. Add interactivity
|
||||
### Test Incrementally
|
||||
1. Start with solid colors
|
||||
2. Add simple effects like pulse
|
||||
3. Experiment with sequences
|
||||
4. Combine multiple animations
|
||||
|
||||
### 5. Consider Performance
|
||||
- Limit simultaneous animations (3-5 max)
|
||||
- Use longer periods for smoother performance
|
||||
- Reuse value providers when possible
|
||||
- Clear animations when switching effects
|
||||
### Performance Considerations
|
||||
- Use sequences instead of multiple simultaneous animations
|
||||
- Reuse value providers with the `set` keyword
|
||||
- Keep animation periods reasonable (>500ms)
|
||||
- Limit palette sizes for memory efficiency
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[API Reference](API_REFERENCE.md)** - Complete API documentation
|
||||
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - DSL syntax guide
|
||||
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom functions
|
||||
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Interactive animations
|
||||
- **[DSL Reference](DSL_REFERENCE.md)** - Complete language syntax
|
||||
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
- **[Animation Development](ANIMATION_DEVELOPMENT.md)** - Creating custom animations
|
||||
|
||||
Experiment with these examples and create your own amazing LED animations! 🎨✨
|
||||
Start with these examples and build your own amazing LED animations! 🎨✨
|
||||
262
lib/libesp32/berry_animation/docs/OSCILLATION_PATTERNS.md
Normal file
262
lib/libesp32/berry_animation/docs/OSCILLATION_PATTERNS.md
Normal file
@ -0,0 +1,262 @@
|
||||
# Oscillation Patterns
|
||||
|
||||
Quick reference for oscillation patterns used with value providers in the Berry Animation Framework.
|
||||
|
||||
## Available Oscillation Patterns
|
||||
|
||||
These waveform constants can be used with `oscillator_value`:
|
||||
|
||||
| Constant | Value | Alias Functions | Behavior | Use Case |
|
||||
|----------|-------|-----------------|----------|----------|
|
||||
| `SAWTOOTH` | 1 | `linear`, `ramp` | Linear ramp up | Uniform motion |
|
||||
| `TRIANGLE` | 2 | `triangle` | Linear up then down | Sharp direction changes |
|
||||
| `SQUARE` | 3 | `square` | Alternating min/max | On/off effects |
|
||||
| `COSINE` | 4 | `smooth` | Smooth cosine wave | Natural oscillation |
|
||||
| `SINE` | 5 | `sine` | Pure sine wave | Classic wave motion |
|
||||
| `EASE_IN` | 6 | `ease_in` | Slow start, fast end | Smooth acceleration |
|
||||
| `EASE_OUT` | 7 | `ease_out` | Fast start, slow end | Smooth deceleration |
|
||||
| `ELASTIC` | 8 | `elastic` | Spring overshoot | Bouncy effects |
|
||||
| `BOUNCE` | 9 | `bounce` | Ball bouncing | Physics simulation |
|
||||
|
||||
## DSL Usage
|
||||
|
||||
### With Oscillator Value Provider
|
||||
```dsl
|
||||
# Basic oscillator with different waveform types
|
||||
set breathing = oscillator_value(min_value=50, max_value=255, duration=3000, form=COSINE)
|
||||
set pulsing = ease_in(min_value=0, max_value=255, duration=2000)
|
||||
set bouncing = oscillator_value(min_value=10, max_value=240, duration=4000, form=TRIANGLE)
|
||||
```
|
||||
|
||||
### Using Alias Functions
|
||||
```dsl
|
||||
# These are equivalent to oscillator_value with specific forms
|
||||
set smooth_fade = smooth(min_value=50, max_value=255, duration=3000) # form=COSINE
|
||||
set sine_wave = sine(min_value=50, max_value=255, duration=3000) # form=SINE
|
||||
set linear_sweep = linear(min_value=0, max_value=255, duration=2000) # form=SAWTOOTH
|
||||
set triangle_wave = triangle(min_value=10, max_value=240, duration=4000) # form=TRIANGLE
|
||||
```
|
||||
|
||||
### In Animations
|
||||
```dsl
|
||||
color blue = 0x0000FF
|
||||
set breathing = smooth(min_value=100, max_value=255, duration=4000)
|
||||
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = breathing
|
||||
run breathing_blue
|
||||
```
|
||||
|
||||
## Pattern Characteristics
|
||||
|
||||
### SAWTOOTH (Linear)
|
||||
- **Constant speed** throughout the cycle
|
||||
- **Sharp reset** from max back to min
|
||||
- **Best for**: Uniform sweeps, mechanical movements
|
||||
|
||||
```
|
||||
Value
|
||||
^
|
||||
| /| /|
|
||||
| / | / |
|
||||
| / | / |
|
||||
| / | / |
|
||||
| / | / |
|
||||
|/ |/ |
|
||||
+------+------+----> Time
|
||||
```
|
||||
|
||||
```dsl
|
||||
set linear_brightness = linear(min_value=0, max_value=255, duration=2000)
|
||||
```
|
||||
|
||||
### COSINE (Smooth)
|
||||
- **Gradual acceleration** and deceleration
|
||||
- **Natural feeling** transitions
|
||||
- **Best for**: Breathing effects, gentle fades
|
||||
|
||||
```dsl
|
||||
set breathing_effect = smooth(min_value=50, max_value=255, duration=3000)
|
||||
```
|
||||
|
||||
### SINE (Pure Wave)
|
||||
- **Classic sine wave** starting from minimum
|
||||
- **Smooth acceleration** and deceleration like cosine but phase-shifted
|
||||
- **Best for**: Wave effects, classic oscillations, audio-visual sync
|
||||
|
||||
```
|
||||
Value
|
||||
^
|
||||
| ___
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
|/ \___
|
||||
+--------------------+----> Time
|
||||
```
|
||||
|
||||
```dsl
|
||||
set wave_motion = sine(min_value=0, max_value=255, duration=2000)
|
||||
```
|
||||
|
||||
### TRIANGLE
|
||||
- **Linear acceleration** to midpoint, then **linear deceleration**
|
||||
- **Sharp direction changes** at extremes
|
||||
- **Best for**: Bouncing effects, sharp transitions
|
||||
|
||||
```
|
||||
Value
|
||||
^
|
||||
| /\
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
| / \
|
||||
|/ \
|
||||
+-------------+----> Time
|
||||
```
|
||||
|
||||
```dsl
|
||||
set bounce_position = triangle(min_value=5, max_value=55, duration=2000)
|
||||
```
|
||||
|
||||
### SQUARE
|
||||
- **Alternating** between min and max values
|
||||
- **Instant transitions** with configurable duty cycle
|
||||
- **Best for**: On/off effects, strobing, digital patterns
|
||||
|
||||
```
|
||||
Value
|
||||
^
|
||||
| +---+ +---+
|
||||
| | | | |
|
||||
| | | | |
|
||||
| | +-----+ |
|
||||
| | |
|
||||
| | |
|
||||
+-+-------------+----> Time
|
||||
```
|
||||
|
||||
```dsl
|
||||
set strobe_effect = square(min_value=0, max_value=255, duration=500, duty_cycle=25)
|
||||
```
|
||||
|
||||
### EASE_IN
|
||||
- **Slow start**, **fast finish**
|
||||
- **Smooth acceleration** curve
|
||||
- **Best for**: Starting animations, building intensity
|
||||
|
||||
```dsl
|
||||
set accelerating = ease_in(min_value=0, max_value=255, duration=3000)
|
||||
```
|
||||
|
||||
### EASE_OUT
|
||||
- **Fast start**, **slow finish**
|
||||
- **Smooth deceleration** curve
|
||||
- **Best for**: Ending animations, gentle stops
|
||||
|
||||
```dsl
|
||||
set decelerating = ease_out(min_value=255, max_value=0, duration=3000)
|
||||
```
|
||||
|
||||
## Value Progression Examples
|
||||
|
||||
For a cycle from 0 to 100 over 2000ms:
|
||||
|
||||
| Time | SAWTOOTH | COSINE | SINE | TRIANGLE | EASE_IN | EASE_OUT |
|
||||
|------|----------|--------|------|----------|---------|----------|
|
||||
| 0ms | 0 | 0 | 0 | 0 | 0 | 0 |
|
||||
| 500ms| 25 | 15 | 50 | 50 | 6 | 44 |
|
||||
| 1000ms| 50 | 50 | 100 | 100 | 25 | 75 |
|
||||
| 1500ms| 75 | 85 | 50 | 50 | 56 | 94 |
|
||||
| 2000ms| 100 | 100 | 0 | 0 | 100 | 100 |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Breathing Effect
|
||||
```dsl
|
||||
color soft_white = 0xC0C0C0
|
||||
set breathing = smooth(min_value=80, max_value=255, duration=4000)
|
||||
|
||||
animation breathing_light = solid(color=soft_white)
|
||||
breathing_light.opacity = breathing
|
||||
run breathing_light
|
||||
```
|
||||
|
||||
### Position Sweep
|
||||
```dsl
|
||||
strip length 60
|
||||
color red = 0xFF0000
|
||||
set sweeping_position = linear(min_value=0, max_value=59, duration=3000)
|
||||
|
||||
animation position_sweep = beacon_animation(
|
||||
color=red,
|
||||
position=sweeping_position,
|
||||
beacon_size=3,
|
||||
fade_size=1
|
||||
)
|
||||
run position_sweep
|
||||
```
|
||||
|
||||
### Wave Motion
|
||||
```dsl
|
||||
color purple = 0x8000FF
|
||||
set wave_brightness = sine(min_value=50, max_value=255, duration=2500)
|
||||
|
||||
animation wave_effect = solid(color=purple)
|
||||
wave_effect.opacity = wave_brightness
|
||||
run wave_effect
|
||||
```
|
||||
|
||||
### Bouncing Effect
|
||||
```dsl
|
||||
color green = 0x00FF00
|
||||
set bounce_size = triangle(min_value=1, max_value=8, duration=1000)
|
||||
|
||||
animation bouncing_pulse = beacon_animation(
|
||||
color=green,
|
||||
position=30,
|
||||
beacon_size=bounce_size,
|
||||
fade_size=1
|
||||
)
|
||||
run bouncing_pulse
|
||||
```
|
||||
|
||||
### Accelerating Fade
|
||||
```dsl
|
||||
color blue = 0x0000FF
|
||||
set fade_in = ease_in(min_value=0, max_value=255, duration=5000)
|
||||
|
||||
animation accelerating_fade = solid(color=blue)
|
||||
accelerating_fade.opacity = fade_in
|
||||
run accelerating_fade
|
||||
```
|
||||
|
||||
### Strobe Effect
|
||||
```dsl
|
||||
color white = 0xFFFFFF
|
||||
set strobe_pattern = square(min_value=0, max_value=255, duration=200, duty_cycle=10)
|
||||
|
||||
animation strobe_light = solid(color=white)
|
||||
strobe_light.opacity = strobe_pattern
|
||||
run strobe_light
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- **COSINE (smooth)**: Most natural for breathing and gentle effects
|
||||
- **SINE**: Classic wave motion, perfect for audio-visual sync and pure oscillations
|
||||
- **SAWTOOTH (linear)**: Best for consistent sweeps and mechanical movements
|
||||
- **TRIANGLE**: Creates sharp, bouncing transitions
|
||||
- **EASE_IN**: Perfect for building up intensity
|
||||
- **EASE_OUT**: Ideal for gentle fade-outs
|
||||
- **ELASTIC**: Spring-like effects with overshoot
|
||||
- **BOUNCE**: Physics-based bouncing effects
|
||||
- **SQUARE**: Good for on/off blinking effects
|
||||
|
||||
Choose the oscillation pattern that matches the feeling you want to create in your animation.
|
||||
@ -1,231 +0,0 @@
|
||||
# Project Structure
|
||||
|
||||
This document explains the organization of the Tasmota Berry Animation Framework project.
|
||||
|
||||
## Root Directory
|
||||
|
||||
```
|
||||
├── README.md # Main project overview and quick start
|
||||
├── docs/ # User documentation
|
||||
├── lib/libesp32/berry_animation/ # Framework source code
|
||||
├── anim_examples/ # DSL animation examples (.anim files)
|
||||
├── anim_examples/compiled/ # Compiled Berry code from DSL examples
|
||||
├── .kiro/ # Project specifications and design docs
|
||||
└── .docs_archive/ # Archived technical implementation docs
|
||||
```
|
||||
|
||||
## Documentation (`docs/`)
|
||||
|
||||
User-focused documentation for learning and using the framework:
|
||||
|
||||
```
|
||||
docs/
|
||||
├── QUICK_START.md # 5-minute getting started guide
|
||||
├── API_REFERENCE.md # Complete Berry API documentation
|
||||
├── EXAMPLES.md # Curated examples with explanations
|
||||
├── TROUBLESHOOTING.md # Common issues and solutions
|
||||
└── PROJECT_STRUCTURE.md # This file
|
||||
```
|
||||
|
||||
## Framework Source Code (`lib/libesp32/berry_animation/`)
|
||||
|
||||
The complete framework implementation:
|
||||
|
||||
```
|
||||
lib/libesp32/berry_animation/
|
||||
├── animation.be # Main module entry point
|
||||
├── README.md # Framework-specific readme
|
||||
├── user_functions.be # Example user-defined functions
|
||||
│
|
||||
├── core/ # Core framework classes
|
||||
│ ├── animation_base.be # Animation base class
|
||||
│ ├── animation_engine.be # Unified animation engine
|
||||
│ ├── event_handler.be # Event system
|
||||
│ ├── frame_buffer.be # Frame buffer management
|
||||
│ ├── pattern_base.be # Pattern base class
|
||||
│ ├── sequence_manager.be # Sequence orchestration
|
||||
│ └── user_functions.be # User function registry
|
||||
│
|
||||
├── effects/ # Animation implementations
|
||||
│ ├── breathe.be # Breathing animation
|
||||
│ ├── comet.be # Comet effect
|
||||
│ ├── crenel_position.be # Rectangular pulse patterns
|
||||
│ ├── filled.be # Filled animations
|
||||
│ ├── fire.be # Fire simulation
|
||||
│ ├── palette_pattern.be # Palette-based patterns
|
||||
│ ├── palettes.be # Predefined palettes
|
||||
│ ├── pattern_animation.be # Pattern wrapper animation
|
||||
│ ├── pulse.be # Pulse animation
|
||||
│ ├── pulse_position.be # Position-based pulse
|
||||
│ └── twinkle.be # Twinkling stars
|
||||
│
|
||||
├── patterns/ # Pattern implementations
|
||||
│ └── solid_pattern.be # Solid color pattern
|
||||
│
|
||||
├── providers/ # Value and color providers
|
||||
│ ├── color_cycle_color_provider.be # Color cycling
|
||||
│ ├── color_provider.be # Base color provider
|
||||
│ ├── composite_color_provider.be # Blended colors
|
||||
│ ├── oscillator_value_provider.be # Waveform generators
|
||||
│ ├── rich_palette_color_provider.be # Palette-based colors
|
||||
│ ├── solid_color_provider.be # Static colors
|
||||
│ ├── static_value_provider.be # Static value wrapper
|
||||
│ └── value_provider.be # Base value provider
|
||||
│
|
||||
├── dsl/ # Domain-Specific Language
|
||||
│ ├── lexer.be # DSL tokenizer
|
||||
│ ├── runtime.be # DSL execution runtime
|
||||
│ ├── token.be # Token definitions
|
||||
│ └── transpiler.be # DSL to Berry transpiler
|
||||
│
|
||||
├── tests/ # Comprehensive test suite
|
||||
│ ├── test_all.be # Run all tests
|
||||
│ ├── animation_engine_test.be
|
||||
│ ├── dsl_transpiler_test.be
|
||||
│ ├── event_system_test.be
|
||||
│ └── ... (50+ test files)
|
||||
│
|
||||
├── examples/ # Berry code examples
|
||||
│ ├── run_all_demos.be # Run all examples
|
||||
│ ├── simple_engine_test.be # Basic usage
|
||||
│ ├── color_provider_demo.be # Color system demo
|
||||
│ ├── event_system_demo.be # Interactive animations
|
||||
│ └── ... (60+ example files)
|
||||
│
|
||||
└── docs/ # Framework-specific documentation
|
||||
├── architecture_simplification.md
|
||||
├── class_hierarchy_reference.md
|
||||
├── migration_guide.md
|
||||
└── ... (technical documentation)
|
||||
```
|
||||
|
||||
## DSL Examples (`anim_examples/`)
|
||||
|
||||
Ready-to-use animation files in DSL format:
|
||||
|
||||
```
|
||||
anim_examples/
|
||||
├── aurora_borealis.anim # Northern lights effect
|
||||
├── breathing_colors.anim # Smooth color breathing
|
||||
├── fire_demo.anim # Realistic fire simulation
|
||||
├── palette_demo.anim # Palette showcase
|
||||
├── rainbow_cycle.anim # Rainbow color cycling
|
||||
└── simple_pulse.anim # Basic pulsing effect
|
||||
```
|
||||
|
||||
## Compiled Examples (`anim_examples/compiled/`)
|
||||
|
||||
Berry code generated from DSL examples (for reference):
|
||||
|
||||
```
|
||||
anim_examples/compiled/
|
||||
├── aurora_borealis.be # Compiled from aurora_borealis.anim
|
||||
├── breathing_colors.be # Compiled from breathing_colors.anim
|
||||
└── ... (compiled versions of .anim files)
|
||||
```
|
||||
|
||||
## Project Specifications (`.kiro/specs/berry-animation-framework/`)
|
||||
|
||||
Design documents and specifications:
|
||||
|
||||
```
|
||||
.kiro/specs/berry-animation-framework/
|
||||
├── design.md # Architecture overview
|
||||
├── requirements.md # Project requirements (✅ complete)
|
||||
├── dsl-specification.md # DSL syntax reference
|
||||
├── dsl-grammar.md # DSL grammar specification
|
||||
├── EVENT_SYSTEM.md # Event system documentation
|
||||
├── USER_FUNCTIONS.md # User-defined functions guide
|
||||
├── palette-quick-reference.md # Palette usage guide
|
||||
└── future_features.md # Planned enhancements
|
||||
```
|
||||
|
||||
## Archived Documentation (`.docs_archive/`)
|
||||
|
||||
Technical implementation documents moved from active documentation:
|
||||
|
||||
```
|
||||
.docs_archive/
|
||||
├── dsl-transpiler-architecture.md # Detailed transpiler design
|
||||
├── dsl-implementation.md # Implementation details
|
||||
├── color_provider_system.md # Color system internals
|
||||
├── unified-architecture-summary.md # Architecture migration notes
|
||||
└── ... (50+ archived technical docs)
|
||||
```
|
||||
|
||||
## Key Files for Different Use Cases
|
||||
|
||||
### Getting Started
|
||||
1. **`README.md`** - Project overview and quick examples
|
||||
2. **`docs/QUICK_START.md`** - 5-minute tutorial
|
||||
3. **`anim_examples/simple_pulse.anim`** - Basic DSL example
|
||||
|
||||
### Learning the API
|
||||
1. **`docs/API_REFERENCE.md`** - Complete API documentation
|
||||
2. **`docs/EXAMPLES.md`** - Curated examples with explanations
|
||||
3. **`lib/libesp32/berry_animation/examples/simple_engine_test.be`** - Basic Berry usage
|
||||
|
||||
### Using the DSL
|
||||
1. **`.kiro/specs/berry-animation-framework/dsl-specification.md`** - Complete DSL syntax
|
||||
2. **`.kiro/specs/berry-animation-framework/palette-quick-reference.md`** - Palette guide
|
||||
3. **`anim_examples/`** - Working DSL examples
|
||||
|
||||
### Advanced Features
|
||||
1. **`.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md`** - Custom functions
|
||||
2. **`.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md`** - Interactive animations
|
||||
3. **`lib/libesp32/berry_animation/user_functions.be`** - Example custom functions
|
||||
|
||||
### Troubleshooting
|
||||
1. **`docs/TROUBLESHOOTING.md`** - Common issues and solutions
|
||||
2. **`lib/libesp32/berry_animation/tests/test_all.be`** - Run all tests
|
||||
3. **`lib/libesp32/berry_animation/examples/run_all_demos.be`** - Test examples
|
||||
|
||||
### Framework Development
|
||||
1. **`.kiro/specs/berry-animation-framework/design.md`** - Architecture overview
|
||||
2. **`.kiro/specs/berry-animation-framework/requirements.md`** - Project requirements
|
||||
3. **`lib/libesp32/berry_animation/tests/`** - Test suite for development
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
### Source Code
|
||||
- **`*.be`** - Berry source files
|
||||
- **`*_test.be`** - Test files
|
||||
- **`*_demo.be`** - Example/demonstration files
|
||||
|
||||
### Documentation
|
||||
- **`*.md`** - Markdown documentation
|
||||
- **`README.md`** - Overview documents
|
||||
- **`QUICK_START.md`** - Getting started guides
|
||||
- **`API_REFERENCE.md`** - API documentation
|
||||
|
||||
### DSL Files
|
||||
- **`*.anim`** - DSL animation files
|
||||
- **`*.be`** (in anim_examples/compiled/) - Compiled Berry code from DSL
|
||||
|
||||
## Navigation Tips
|
||||
|
||||
### For New Users
|
||||
1. Start with `README.md` for project overview
|
||||
2. Follow `docs/QUICK_START.md` for hands-on tutorial
|
||||
3. Browse `anim_examples/` for inspiration
|
||||
4. Reference `docs/API_REFERENCE.md` when needed
|
||||
|
||||
### For DSL Users
|
||||
1. Learn syntax from `.kiro/specs/berry-animation-framework/dsl-specification.md`
|
||||
2. Study examples in `anim_examples/`
|
||||
3. Use palette guide: `.kiro/specs/berry-animation-framework/palette-quick-reference.md`
|
||||
4. Create custom functions: `.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md`
|
||||
|
||||
### For Berry Developers
|
||||
1. Study `lib/libesp32/berry_animation/examples/simple_engine_test.be`
|
||||
2. Reference `docs/API_REFERENCE.md` for complete API
|
||||
3. Run tests: `lib/libesp32/berry_animation/tests/test_all.be`
|
||||
4. Explore advanced examples in `lib/libesp32/berry_animation/examples/`
|
||||
|
||||
### For Framework Contributors
|
||||
1. Understand architecture: `.kiro/specs/berry-animation-framework/design.md`
|
||||
2. Review requirements: `.kiro/specs/berry-animation-framework/requirements.md`
|
||||
3. Study source code in `lib/libesp32/berry_animation/core/`
|
||||
4. Run comprehensive tests: `lib/libesp32/berry_animation/tests/test_all.be`
|
||||
|
||||
This structure provides clear separation between user documentation, source code, examples, and technical specifications, making it easy to find relevant information for any use case.
|
||||
@ -1,94 +1,45 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get up and running with the Tasmota Berry Animation Framework in 5 minutes!
|
||||
Get up and running with the Berry Animation Framework in 5 minutes using the DSL!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Tasmota device with Berry support
|
||||
- Addressable LED strip (WS2812, SK6812, etc.)
|
||||
- Basic familiarity with Tasmota console
|
||||
|
||||
## Step 1: Basic Setup
|
||||
## Step 1: Your First Animation
|
||||
|
||||
### Import the Framework
|
||||
```berry
|
||||
import animation
|
||||
```
|
||||
Create a simple pulsing red light:
|
||||
|
||||
### Create LED Strip and Engine
|
||||
```berry
|
||||
# Create LED strip (adjust count for your setup)
|
||||
var strip = Leds(30) # 30 LEDs
|
||||
|
||||
# Create animation engine
|
||||
var engine = animation.create_engine(strip)
|
||||
```
|
||||
|
||||
## Step 2: Your First Animation
|
||||
|
||||
### Simple Solid Color
|
||||
```berry
|
||||
# Create a solid red animation
|
||||
var red_anim = animation.solid(0xFFFF0000) # ARGB format
|
||||
|
||||
# Add to engine and start
|
||||
engine.add_animation(red_anim)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### Pulsing Effect
|
||||
```berry
|
||||
# Create pulsing blue animation
|
||||
var pulse_blue = animation.pulse(
|
||||
animation.solid(0xFF0000FF), # Blue color
|
||||
2000, # 2 second period
|
||||
50, # Min brightness (0-255)
|
||||
255 # Max brightness (0-255)
|
||||
)
|
||||
|
||||
engine.clear() # Clear previous animations
|
||||
engine.add_animation(pulse_blue)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
## Step 3: Using the DSL
|
||||
|
||||
The DSL (Domain-Specific Language) makes animations much easier to write.
|
||||
|
||||
### Create Animation File
|
||||
Create `my_first.anim`:
|
||||
```dsl
|
||||
# Define colors
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Create pulsing animation
|
||||
animation pulse_red = pulse(solid(red), 3s, 20%, 100%)
|
||||
animation pulse_red = pulsating_animation(color=red, period=3s)
|
||||
|
||||
# Run it
|
||||
run pulse_red
|
||||
```
|
||||
|
||||
### Load DSL Animation
|
||||
```berry
|
||||
import animation
|
||||
## Step 2: Color Cycling
|
||||
|
||||
var strip = Leds(30)
|
||||
var runtime = animation.DSLRuntime(animation.create_engine(strip))
|
||||
Create smooth color transitions:
|
||||
|
||||
# Load from string
|
||||
var dsl_code = '''
|
||||
color blue = #0000FF
|
||||
animation pulse_blue = pulse(solid(blue), 2s, 30%, 100%)
|
||||
run pulse_blue
|
||||
'''
|
||||
```dsl
|
||||
# Use predefined rainbow palette
|
||||
animation rainbow_cycle = rich_palette(
|
||||
palette=PALETTE_RAINBOW,
|
||||
cycle_period=5s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
runtime.load_dsl(dsl_code)
|
||||
run rainbow_cycle
|
||||
```
|
||||
|
||||
## Step 4: Color Palettes
|
||||
## Step 3: Custom Palettes
|
||||
|
||||
Palettes create smooth color transitions:
|
||||
Create your own color palettes:
|
||||
|
||||
```dsl
|
||||
# Define a sunset palette
|
||||
@ -101,23 +52,23 @@ palette sunset = [
|
||||
]
|
||||
|
||||
# Create palette animation
|
||||
animation sunset_glow = rich_palette_animation(sunset, 5s, smooth, 200)
|
||||
animation sunset_glow = rich_palette(
|
||||
palette=sunset,
|
||||
cycle_period=8s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
run sunset_glow
|
||||
```
|
||||
|
||||
## Step 5: Sequences
|
||||
## Step 4: Sequences
|
||||
|
||||
Create complex shows with sequences:
|
||||
|
||||
```dsl
|
||||
color red = #FF0000
|
||||
color green = #00FF00
|
||||
color blue = #0000FF
|
||||
|
||||
animation red_pulse = pulse(solid(red), 2s, 50%, 100%)
|
||||
animation green_pulse = pulse(solid(green), 2s, 50%, 100%)
|
||||
animation blue_pulse = pulse(solid(blue), 2s, 50%, 100%)
|
||||
animation red_pulse = pulsating_animation(color=red, period=2s)
|
||||
animation green_pulse = pulsating_animation(color=green, period=2s)
|
||||
animation blue_pulse = pulsating_animation(color=blue, period=2s)
|
||||
|
||||
sequence rgb_show {
|
||||
play red_pulse for 3s
|
||||
@ -125,7 +76,7 @@ sequence rgb_show {
|
||||
play green_pulse for 3s
|
||||
wait 500ms
|
||||
play blue_pulse for 3s
|
||||
wait 500ms
|
||||
|
||||
repeat 2 times:
|
||||
play red_pulse for 1s
|
||||
play green_pulse for 1s
|
||||
@ -135,119 +86,112 @@ sequence rgb_show {
|
||||
run rgb_show
|
||||
```
|
||||
|
||||
## Step 6: Interactive Animations
|
||||
## Step 5: Dynamic Effects
|
||||
|
||||
Add event handling for interactive effects:
|
||||
Add movement and variation to your animations:
|
||||
|
||||
```dsl
|
||||
color white = #FFFFFF
|
||||
color red = #FF0000
|
||||
# Breathing effect with smooth oscillation
|
||||
animation breathing = pulsating_animation(
|
||||
color=blue,
|
||||
min_brightness=50,
|
||||
max_brightness=255,
|
||||
period=4s
|
||||
)
|
||||
|
||||
animation flash_white = solid(white)
|
||||
animation normal_red = solid(red)
|
||||
# Moving comet effect
|
||||
animation comet = comet_animation(
|
||||
color=white,
|
||||
tail_length=8,
|
||||
speed=2000
|
||||
)
|
||||
|
||||
# Flash white when button pressed
|
||||
on button_press: flash_white
|
||||
# Sparkle effect
|
||||
animation sparkles = sparkle_animation(
|
||||
color=white,
|
||||
density=80,
|
||||
fade_speed=60
|
||||
)
|
||||
|
||||
# Main animation
|
||||
run normal_red
|
||||
run breathing
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Fire Effect
|
||||
```dsl
|
||||
palette fire = [
|
||||
(0, #000000), # Black
|
||||
(64, #800000), # Dark red
|
||||
(128, #FF0000), # Red
|
||||
(192, #FF8000), # Orange
|
||||
(255, #FFFF00) # Yellow
|
||||
]
|
||||
animation fire = rich_palette(
|
||||
palette=PALETTE_FIRE,
|
||||
cycle_period=2s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
animation fire_effect = rich_palette_animation(fire, 2s, smooth, 255)
|
||||
run fire_effect
|
||||
run fire
|
||||
```
|
||||
|
||||
### Rainbow Cycle
|
||||
### Ocean Waves
|
||||
```dsl
|
||||
palette rainbow = [
|
||||
(0, red), (42, orange), (84, yellow),
|
||||
(126, green), (168, blue), (210, indigo), (255, violet)
|
||||
]
|
||||
animation ocean = rich_palette(
|
||||
palette=PALETTE_OCEAN,
|
||||
cycle_period=6s,
|
||||
transition_type=1
|
||||
)
|
||||
|
||||
animation rainbow_cycle = rich_palette_animation(rainbow, 10s, smooth, 255)
|
||||
run rainbow_cycle
|
||||
```
|
||||
|
||||
### Breathing Effect
|
||||
```dsl
|
||||
color soft_blue = #4080FF
|
||||
animation breathing = pulse(solid(soft_blue), 4s, 10%, 100%)
|
||||
run breathing
|
||||
run ocean
|
||||
```
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### 1. Start Simple
|
||||
Begin with solid colors and basic pulses before moving to complex effects.
|
||||
1. **Start Simple** - Begin with solid colors and basic effects
|
||||
2. **Use Predefined Palettes** - Try PALETTE_RAINBOW, PALETTE_FIRE, PALETTE_OCEAN
|
||||
3. **Test Incrementally** - Add one animation at a time
|
||||
4. **Use Named Colors** - red, blue, green, white, etc.
|
||||
5. **Start with Longer Periods** - 3-5 seconds, then adjust as needed
|
||||
|
||||
### 2. Use the DSL
|
||||
The DSL is much easier than writing Berry code directly.
|
||||
## Loading DSL Files
|
||||
|
||||
### 3. Test Incrementally
|
||||
Add one animation at a time and test before adding complexity.
|
||||
Save your DSL code in `.anim` files and load them:
|
||||
|
||||
### 4. Check Your Colors
|
||||
Use hex color codes (#RRGGBB) or named colors (red, blue, green).
|
||||
|
||||
### 5. Mind the Timing
|
||||
Start with longer periods (3-5 seconds) and adjust as needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Animation Not Starting
|
||||
```berry
|
||||
# Make sure to start the engine
|
||||
engine.start()
|
||||
import animation
|
||||
|
||||
# Check if animation was added
|
||||
print(engine.size()) # Should be > 0
|
||||
# Load DSL file
|
||||
var runtime = animation.load_dsl_file("my_animation.anim")
|
||||
```
|
||||
|
||||
### Colors Look Wrong
|
||||
```berry
|
||||
# Check color format (ARGB with alpha channel)
|
||||
var red = 0xFFFF0000 # Correct: Alpha=FF, Red=FF, Green=00, Blue=00
|
||||
var red = 0xFF0000 # Wrong: Missing alpha channel
|
||||
```
|
||||
## User-Defined Functions
|
||||
|
||||
Create custom animation functions in Berry and use them in DSL:
|
||||
|
||||
### DSL Compilation Errors
|
||||
```berry
|
||||
# Use try/catch for better error messages
|
||||
try
|
||||
runtime.load_dsl(dsl_code)
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("DSL Error:", msg)
|
||||
# Define custom function - engine must be first parameter
|
||||
def my_sparkle(engine, color, density, speed)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
return anim
|
||||
end
|
||||
|
||||
# Register for DSL use
|
||||
animation.register_user_function("sparkle", my_sparkle)
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
```berry
|
||||
# Limit number of simultaneous animations
|
||||
engine.clear() # Remove all animations
|
||||
engine.add_animation(new_animation) # Add just one
|
||||
|
||||
# Use longer periods for smoother performance
|
||||
animation pulse_slow = pulse(solid(red), 5s, 50%, 100%) # 5 seconds instead of 1
|
||||
```dsl
|
||||
# Use in DSL - engine is automatically passed
|
||||
animation gold_sparkles = sparkle(#FFD700, 8, 500ms)
|
||||
run gold_sparkles
|
||||
```
|
||||
|
||||
**Note**: The DSL automatically passes `engine` as the first argument to user functions.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax
|
||||
- **[API Reference](API_REFERENCE.md)** - Berry API documentation
|
||||
- **[Examples](EXAMPLES.md)** - More complex examples
|
||||
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom functions
|
||||
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Interactive animations
|
||||
- **[DSL Reference](DSL_REFERENCE.md)** - Complete DSL syntax and features
|
||||
- **[User Functions](USER_FUNCTIONS.md)** - Create custom animation functions
|
||||
- **[Examples](EXAMPLES.md)** - More complex animation examples
|
||||
- **[Animation Class Hierarchy](ANIMATION_CLASS_HIERARCHY.md)** - All available animations and parameters
|
||||
- **[Oscillation Patterns](OSCILLATION_PATTERNS.md)** - Dynamic value patterns
|
||||
- **[Troubleshooting](TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
|
||||
Happy animating! 🎨✨
|
||||
@ -2,18 +2,19 @@
|
||||
|
||||
Common issues and solutions for the Tasmota Berry Animation Framework.
|
||||
|
||||
**Note**: This guide focuses on DSL usage, which is the recommended way to create animations. For programmatic API issues, see the [Animation Development Guide](ANIMATION_DEVELOPMENT.md).
|
||||
|
||||
## Installation Issues
|
||||
|
||||
### Framework Not Found
|
||||
|
||||
**Problem:** `import animation` fails with "module not found"
|
||||
**Problem:** `import animation` or `import animation_dsl` fails with "module not found"
|
||||
|
||||
**Solutions:**
|
||||
1. **Check Module Path:**
|
||||
1. **Check Module Import:**
|
||||
```berry
|
||||
# Verify the animation module exists
|
||||
import sys
|
||||
print(sys.path())
|
||||
import animation # Core framework
|
||||
import animation_dsl # DSL compiler
|
||||
```
|
||||
|
||||
2. **Set Module Path:**
|
||||
@ -25,8 +26,9 @@ Common issues and solutions for the Tasmota Berry Animation Framework.
|
||||
```
|
||||
lib/libesp32/berry_animation/
|
||||
├── animation.be # Main module file
|
||||
├── dsl/ # DSL components
|
||||
├── core/ # Core classes
|
||||
├── effects/ # Animation effects
|
||||
├── animations/ # Animation effects
|
||||
└── ...
|
||||
```
|
||||
|
||||
@ -56,47 +58,54 @@ Common issues and solutions for the Tasmota Berry Animation Framework.
|
||||
|
||||
### Animations Not Starting
|
||||
|
||||
**Problem:** Animations created but LEDs don't change
|
||||
**Problem:** DSL animations compile but LEDs don't change
|
||||
|
||||
**Diagnostic Steps:**
|
||||
```berry
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
var anim = animation.solid(0xFFFF0000)
|
||||
# Test basic DSL execution
|
||||
var dsl_code = "color red = 0xFF0000\n" +
|
||||
"animation red_anim = solid(color=red)\n" +
|
||||
"run red_anim"
|
||||
|
||||
# Check each step
|
||||
print("Engine created:", engine != nil)
|
||||
print("Animation created:", anim != nil)
|
||||
|
||||
engine.add_animation(anim)
|
||||
print("Animation added, count:", engine.size())
|
||||
|
||||
engine.start()
|
||||
print("Engine started:", engine.is_active())
|
||||
try
|
||||
animation_dsl.execute(dsl_code)
|
||||
print("DSL executed successfully")
|
||||
except .. as e, msg
|
||||
print("DSL Error:", msg)
|
||||
end
|
||||
```
|
||||
|
||||
**Common Solutions:**
|
||||
|
||||
1. **Forgot to Start Engine:**
|
||||
```berry
|
||||
engine.add_animation(anim)
|
||||
engine.start() # Don't forget this!
|
||||
1. **Missing Strip Declaration:**
|
||||
```dsl
|
||||
# Add explicit strip length if needed
|
||||
strip length 30
|
||||
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
run red_anim
|
||||
```
|
||||
|
||||
2. **Animation Not Added:**
|
||||
```berry
|
||||
# Make sure animation is added to engine
|
||||
engine.add_animation(anim)
|
||||
print("Animation count:", engine.size()) # Should be > 0
|
||||
2. **Animation Not Executed:**
|
||||
```dsl
|
||||
# Make sure you have a 'run' statement
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
run red_anim # Don't forget this!
|
||||
```
|
||||
|
||||
3. **Strip Not Configured:**
|
||||
```berry
|
||||
# Check strip configuration
|
||||
var strip = Leds(30) # 30 LEDs
|
||||
print("Strip created:", strip != nil)
|
||||
3. **Strip Auto-Detection Issues:**
|
||||
```dsl
|
||||
# Force strip length if auto-detection fails
|
||||
strip length 30 # Must be first statement
|
||||
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
run red_anim
|
||||
```
|
||||
|
||||
### Colors Look Wrong
|
||||
@ -106,29 +115,36 @@ print("Engine started:", engine.is_active())
|
||||
**Common Issues:**
|
||||
|
||||
1. **Missing Alpha Channel:**
|
||||
```berry
|
||||
# Wrong - missing alpha
|
||||
var red = 0xFF0000
|
||||
```dsl
|
||||
# Note: 0xFF0000 is valid RGB format (alpha defaults to 0xFF)
|
||||
color red = 0xFF0000 # RGB format (alpha=255 assumed)
|
||||
|
||||
# Correct - with alpha channel
|
||||
var red = 0xFFFF0000 # ARGB format
|
||||
# Explicit alpha channel (ARGB format)
|
||||
color red = 0xFFFF0000 # ARGB format (alpha=255, red=255)
|
||||
color semi_red = 0x80FF0000 # ARGB format (alpha=128, red=255)
|
||||
```
|
||||
|
||||
2. **Color Format Confusion:**
|
||||
```berry
|
||||
```dsl
|
||||
# ARGB format: 0xAARRGGBB
|
||||
var red = 0xFFFF0000 # Alpha=FF, Red=FF, Green=00, Blue=00
|
||||
var green = 0xFF00FF00 # Alpha=FF, Red=00, Green=FF, Blue=00
|
||||
var blue = 0xFF0000FF # Alpha=FF, Red=00, Green=00, Blue=FF
|
||||
color red = 0xFFFF0000 # Alpha=FF, Red=FF, Green=00, Blue=00
|
||||
color green = 0xFF00FF00 # Alpha=FF, Red=00, Green=FF, Blue=00
|
||||
color blue = 0xFF0000FF # Alpha=FF, Red=00, Green=00, Blue=FF
|
||||
```
|
||||
|
||||
3. **Brightness Issues:**
|
||||
```berry
|
||||
# Check opacity settings
|
||||
anim.set_opacity(255) # Full brightness
|
||||
```dsl
|
||||
# Use opacity parameter or property assignment
|
||||
animation red_anim = solid(color=red, opacity=255) # Full brightness
|
||||
|
||||
# Check pulse brightness ranges
|
||||
var pulse = animation.pulse(pattern, 2000, 50, 255) # Min=50, Max=255
|
||||
# Or assign after creation
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
pulse_red.opacity = 200 # Adjust brightness
|
||||
|
||||
# Use value providers for dynamic brightness
|
||||
set brightness = smooth(min_value=50, max_value=255, period=3s)
|
||||
animation breathing = solid(color=red)
|
||||
breathing.opacity = brightness
|
||||
```
|
||||
|
||||
### Animations Too Fast/Slow
|
||||
@ -138,28 +154,35 @@ print("Engine started:", engine.is_active())
|
||||
**Solutions:**
|
||||
|
||||
1. **Check Time Units:**
|
||||
```berry
|
||||
# Berry uses milliseconds
|
||||
var pulse = animation.pulse(pattern, 2000, 50, 255) # 2 seconds
|
||||
|
||||
# DSL uses time units
|
||||
# animation pulse_anim = pulse(pattern, 2s, 20%, 100%) # 2 seconds
|
||||
```dsl
|
||||
# DSL uses time units (converted to milliseconds)
|
||||
animation pulse_anim = pulsating_animation(color=red, period=2s) # 2 seconds
|
||||
animation fast_pulse = pulsating_animation(color=blue, period=500ms) # 0.5 seconds
|
||||
```
|
||||
|
||||
2. **Adjust Periods:**
|
||||
```berry
|
||||
```dsl
|
||||
# Too fast - increase period
|
||||
var slow_pulse = animation.pulse(pattern, 5000, 50, 255) # 5 seconds
|
||||
animation slow_pulse = pulsating_animation(color=red, period=5s) # 5 seconds
|
||||
|
||||
# Too slow - decrease period
|
||||
var fast_pulse = animation.pulse(pattern, 500, 50, 255) # 0.5 seconds
|
||||
animation fast_pulse = pulsating_animation(color=red, period=500ms) # 0.5 seconds
|
||||
```
|
||||
|
||||
3. **Performance Limitations:**
|
||||
```berry
|
||||
# Reduce number of simultaneous animations
|
||||
engine.clear() # Remove all animations
|
||||
engine.add_animation(single_animation)
|
||||
```dsl
|
||||
# Use sequences instead of multiple simultaneous animations
|
||||
sequence optimized_show {
|
||||
play animation1 for 3s
|
||||
play animation2 for 3s
|
||||
play animation3 for 3s
|
||||
}
|
||||
run optimized_show
|
||||
|
||||
# Instead of:
|
||||
# run animation1
|
||||
# run animation2
|
||||
# run animation3
|
||||
```
|
||||
|
||||
## DSL Issues
|
||||
@ -171,7 +194,7 @@ print("Engine started:", engine.is_active())
|
||||
**Diagnostic Approach:**
|
||||
```berry
|
||||
try
|
||||
var berry_code = animation.compile_dsl(dsl_source)
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
print("Compilation successful")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("DSL Error:", msg)
|
||||
@ -183,38 +206,63 @@ end
|
||||
1. **Undefined Colors:**
|
||||
```dsl
|
||||
# Wrong - color not defined
|
||||
animation red_anim = solid(red)
|
||||
animation red_anim = solid(color=red)
|
||||
|
||||
# Correct - define color first
|
||||
color red = #FF0000
|
||||
animation red_anim = solid(red)
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
```
|
||||
|
||||
2. **Invalid Color Format:**
|
||||
```dsl
|
||||
# Wrong - invalid hex format
|
||||
color red = FF0000
|
||||
|
||||
# Correct - with # prefix
|
||||
# Wrong - # prefix not supported (conflicts with comments)
|
||||
color red = #FF0000
|
||||
|
||||
# Correct - use 0x prefix
|
||||
color red = 0xFF0000
|
||||
```
|
||||
|
||||
3. **Missing Time Units:**
|
||||
```dsl
|
||||
# Wrong - no time unit
|
||||
animation pulse_anim = pulse(solid(red), 2000, 50%, 100%)
|
||||
animation pulse_anim = pulsating_animation(color=red, period=2000)
|
||||
|
||||
# Correct - with time unit
|
||||
animation pulse_anim = pulse(solid(red), 2s, 50%, 100%)
|
||||
animation pulse_anim = pulsating_animation(color=red, period=2s)
|
||||
```
|
||||
|
||||
4. **Reserved Name Conflicts:**
|
||||
```dsl
|
||||
# Wrong - 'red' is a predefined color
|
||||
color red = #800000
|
||||
color red = 0x800000
|
||||
|
||||
# Correct - use different name
|
||||
color dark_red = #800000
|
||||
color dark_red = 0x800000
|
||||
```
|
||||
|
||||
5. **Invalid Parameter Names:**
|
||||
```dsl
|
||||
# Wrong - invalid parameter name
|
||||
animation pulse_anim = pulsating_animation(color=red, invalid_param=123)
|
||||
# Error: "Parameter 'invalid_param' is not valid for pulsating_animation"
|
||||
|
||||
# Correct - use valid parameters (see DSL_REFERENCE.md for complete list)
|
||||
animation pulse_anim = pulsating_animation(color=red, period=2s)
|
||||
```
|
||||
|
||||
6. **Parameter Constraint Violations:**
|
||||
```dsl
|
||||
# Wrong - negative period not allowed
|
||||
animation bad_pulse = pulsating_animation(color=red, period=-2s)
|
||||
# Error: "Parameter 'period' value -2000 violates constraint: min=1"
|
||||
|
||||
# Wrong - invalid enum value
|
||||
animation bad_comet = comet_animation(color=red, direction=5)
|
||||
# Error: "Parameter 'direction' value 5 not in allowed values: [-1, 1]"
|
||||
|
||||
# Correct - valid parameters within constraints
|
||||
animation good_pulse = pulsating_animation(color=red, period=2s)
|
||||
animation good_comet = comet_animation(color=red, direction=1)
|
||||
```
|
||||
|
||||
### DSL Runtime Errors
|
||||
@ -228,20 +276,40 @@ end
|
||||
# Add strip declaration if needed
|
||||
strip length 30
|
||||
|
||||
color red = #FF0000
|
||||
animation red_anim = solid(red)
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
run red_anim
|
||||
```
|
||||
|
||||
2. **Sequence Issues:**
|
||||
```dsl
|
||||
# Make sure animations are defined before sequences
|
||||
color red = #FF0000
|
||||
animation red_anim = solid(red) # Define first
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red) # Define first
|
||||
|
||||
sequence demo {
|
||||
play red_anim for 3s # Use after definition
|
||||
wait 1s # Optional pause between animations
|
||||
}
|
||||
run demo
|
||||
```
|
||||
|
||||
3. **Undefined References:**
|
||||
```dsl
|
||||
# Wrong - using undefined animation in sequence
|
||||
sequence bad_demo {
|
||||
play undefined_animation for 3s
|
||||
}
|
||||
# Error: "Undefined reference: 'undefined_animation'"
|
||||
|
||||
# Correct - define all references first
|
||||
color blue = 0x0000FF
|
||||
animation blue_anim = solid(color=blue)
|
||||
|
||||
sequence good_demo {
|
||||
play blue_anim for 3s
|
||||
}
|
||||
run good_demo
|
||||
```
|
||||
|
||||
## Performance Issues
|
||||
@ -252,37 +320,44 @@ end
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Reduce Animation Count:**
|
||||
```berry
|
||||
# Good - 1-3 animations
|
||||
engine.clear()
|
||||
engine.add_animation(main_animation)
|
||||
1. **Use Sequences Instead of Multiple Animations:**
|
||||
```dsl
|
||||
# Good - sequential playback
|
||||
sequence smooth_show {
|
||||
play animation1 for 3s
|
||||
play animation2 for 3s
|
||||
play animation3 for 3s
|
||||
}
|
||||
run smooth_show
|
||||
|
||||
# Avoid - too many simultaneous animations
|
||||
# engine.add_animation(anim1)
|
||||
# engine.add_animation(anim2)
|
||||
# ... (10+ animations)
|
||||
# run animation1
|
||||
# run animation2
|
||||
# run animation3
|
||||
```
|
||||
|
||||
2. **Increase Animation Periods:**
|
||||
```berry
|
||||
```dsl
|
||||
# Smooth - longer periods
|
||||
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
|
||||
animation smooth_pulse = pulsating_animation(color=red, period=3s)
|
||||
|
||||
# Choppy - very short periods
|
||||
var choppy_pulse = animation.pulse(pattern, 50, 50, 255) # 50ms
|
||||
animation choppy_pulse = pulsating_animation(color=red, period=50ms)
|
||||
```
|
||||
|
||||
3. **Optimize Value Providers:**
|
||||
```berry
|
||||
```dsl
|
||||
# Efficient - reuse providers
|
||||
var breathing = animation.smooth(50, 255, 2000)
|
||||
var anim1 = animation.pulse(pattern1, breathing)
|
||||
var anim2 = animation.pulse(pattern2, breathing) # Reuse
|
||||
set breathing = smooth(min_value=50, max_value=255, period=2s)
|
||||
|
||||
# Inefficient - create new providers
|
||||
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
|
||||
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000))
|
||||
color red = 0xFF0000
|
||||
color blue = 0x0000FF
|
||||
|
||||
animation anim1 = pulsating_animation(color=red, period=2s)
|
||||
anim1.opacity = breathing
|
||||
|
||||
animation anim2 = pulsating_animation(color=blue, period=2s)
|
||||
anim2.opacity = breathing # Reuse same provider
|
||||
```
|
||||
|
||||
### Memory Issues
|
||||
@ -405,7 +480,15 @@ var strip = Leds(30) # 30 LEDs
|
||||
strip.set_pixel_color(0, 0xFFFF0000) # Set first pixel red
|
||||
strip.show() # Update LEDs
|
||||
|
||||
# If this doesn't work, check hardware
|
||||
# Test with animation framework
|
||||
import animation
|
||||
var engine = animation.create_engine(strip)
|
||||
var red_anim = animation.solid(engine)
|
||||
red_anim.color = 0xFFFF0000
|
||||
engine.add_animation(red_anim)
|
||||
engine.start()
|
||||
|
||||
# If basic strip works but animation doesn't, check framework setup
|
||||
```
|
||||
|
||||
### Wrong Colors on Hardware
|
||||
@ -435,20 +518,45 @@ strip.show() # Update LEDs
|
||||
|
||||
## Debugging Techniques
|
||||
|
||||
### Enable Debug Mode
|
||||
### DSL vs Berry API Debugging
|
||||
|
||||
**For DSL Issues (Recommended):**
|
||||
```berry
|
||||
# Enable debug output
|
||||
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
|
||||
# Enable DSL debug output
|
||||
import animation_dsl
|
||||
|
||||
# Check generated code
|
||||
var dsl_code = "color red = 0xFF0000\nanimation test = solid(color=red)\nrun test"
|
||||
|
||||
# Check compilation
|
||||
try
|
||||
var berry_code = animation.compile_dsl(dsl_source)
|
||||
var berry_code = animation_dsl.compile(dsl_code)
|
||||
print("DSL compilation successful")
|
||||
print("Generated Berry code:")
|
||||
print(berry_code)
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("Compilation error:", msg)
|
||||
except .. as e, msg
|
||||
print("DSL compilation error:", msg)
|
||||
end
|
||||
|
||||
# Execute with debug
|
||||
try
|
||||
animation_dsl.execute(dsl_code, true) # debug=true
|
||||
except .. as e, msg
|
||||
print("DSL execution error:", msg)
|
||||
end
|
||||
```
|
||||
|
||||
**For Framework Issues (Advanced):**
|
||||
```berry
|
||||
# Direct Berry API debugging (for framework developers)
|
||||
import animation
|
||||
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip, true) # debug=true
|
||||
|
||||
var anim = animation.solid(engine)
|
||||
anim.color = 0xFFFF0000
|
||||
engine.add_animation(anim)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### Step-by-Step Testing
|
||||
@ -464,7 +572,8 @@ var engine = animation.create_engine(strip)
|
||||
print("Engine created:", engine != nil)
|
||||
|
||||
print("3. Creating animation...")
|
||||
var anim = animation.solid(0xFFFF0000)
|
||||
var anim = animation.solid(engine)
|
||||
anim.color = 0xFFFF0000
|
||||
print("Animation created:", anim != nil)
|
||||
|
||||
print("4. Adding animation...")
|
||||
@ -529,8 +638,8 @@ When asking for help, include:
|
||||
|
||||
**Code:**
|
||||
```dsl
|
||||
color red = #FF0000
|
||||
animation red_anim = solid(red)
|
||||
color red = 0xFF0000
|
||||
animation red_anim = solid(color=red)
|
||||
run red_anim
|
||||
```
|
||||
|
||||
@ -596,4 +705,54 @@ This format helps identify issues quickly and provide targeted solutions.
|
||||
- Proper wiring and connections
|
||||
- Appropriate LED strip for application
|
||||
|
||||
## Quick Reference: Common DSL Patterns
|
||||
|
||||
### Basic Animation
|
||||
```dsl
|
||||
color red = 0xFF0000
|
||||
animation red_solid = solid(color=red)
|
||||
run red_solid
|
||||
```
|
||||
|
||||
### Animation with Parameters
|
||||
```dsl
|
||||
color blue = 0x0000FF
|
||||
animation blue_pulse = pulsating_animation(color=blue, period=2s, opacity=200)
|
||||
run blue_pulse
|
||||
```
|
||||
|
||||
### Using Value Providers
|
||||
```dsl
|
||||
set breathing = smooth(min_value=50, max_value=255, period=3s)
|
||||
color green = 0x00FF00
|
||||
animation breathing_green = solid(color=green)
|
||||
breathing_green.opacity = breathing
|
||||
run breathing_green
|
||||
```
|
||||
|
||||
### Sequences
|
||||
```dsl
|
||||
color red = 0xFF0000
|
||||
color blue = 0x0000FF
|
||||
|
||||
animation red_anim = solid(color=red)
|
||||
animation blue_anim = solid(color=blue)
|
||||
|
||||
sequence demo {
|
||||
play red_anim for 2s
|
||||
wait 500ms
|
||||
play blue_anim for 2s
|
||||
}
|
||||
run demo
|
||||
```
|
||||
|
||||
### Multiple Strip Lengths
|
||||
```dsl
|
||||
strip length 60 # Must be first statement
|
||||
|
||||
color rainbow = rainbow_color_provider(period=5s)
|
||||
animation rainbow_anim = solid(color=rainbow)
|
||||
run rainbow_anim
|
||||
```
|
||||
|
||||
Following these guidelines will help you avoid most common issues and create reliable LED animations.
|
||||
442
lib/libesp32/berry_animation/docs/USER_FUNCTIONS.md
Normal file
442
lib/libesp32/berry_animation/docs/USER_FUNCTIONS.md
Normal file
@ -0,0 +1,442 @@
|
||||
# User-Defined Functions
|
||||
|
||||
Create custom animation functions in Berry and use them seamlessly in the Animation DSL.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create Your Function
|
||||
|
||||
Write a Berry function that creates and returns an animation:
|
||||
|
||||
```berry
|
||||
# Define a custom breathing effect
|
||||
def my_breathing(engine, color, speed)
|
||||
var anim = animation.pulsating_animation(engine)
|
||||
anim.color = color
|
||||
anim.min_brightness = 50
|
||||
anim.max_brightness = 255
|
||||
anim.period = speed
|
||||
return anim
|
||||
end
|
||||
```
|
||||
|
||||
### 2. Register It
|
||||
|
||||
Make your function available in DSL:
|
||||
|
||||
```berry
|
||||
animation.register_user_function("breathing", my_breathing)
|
||||
```
|
||||
|
||||
### 3. Use It in DSL
|
||||
|
||||
Call your function just like built-in animations:
|
||||
|
||||
```dsl
|
||||
# Use your custom function
|
||||
animation calm = breathing(blue, 4s)
|
||||
animation energetic = breathing(red, 1s)
|
||||
|
||||
sequence demo {
|
||||
play calm for 10s
|
||||
play energetic for 5s
|
||||
}
|
||||
|
||||
run demo
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Simple Color Effects
|
||||
|
||||
```berry
|
||||
def solid_bright(engine, color, brightness_percent)
|
||||
var anim = animation.solid_animation(engine)
|
||||
anim.color = color
|
||||
anim.brightness = int(brightness_percent * 255 / 100)
|
||||
return anim
|
||||
end
|
||||
|
||||
animation.register_user_function("bright", solid_bright)
|
||||
```
|
||||
|
||||
```dsl
|
||||
animation bright_red = bright(red, 80%)
|
||||
animation dim_blue = bright(blue, 30%)
|
||||
```
|
||||
|
||||
### Fire Effects
|
||||
|
||||
```berry
|
||||
def custom_fire(engine, intensity, speed)
|
||||
var color_provider = animation.rich_palette(engine)
|
||||
color_provider.palette = animation.PALETTE_FIRE
|
||||
color_provider.cycle_period = speed
|
||||
|
||||
var fire_anim = animation.filled(engine)
|
||||
fire_anim.color_provider = color_provider
|
||||
fire_anim.brightness = intensity
|
||||
return fire_anim
|
||||
end
|
||||
|
||||
animation.register_user_function("fire", custom_fire)
|
||||
```
|
||||
|
||||
```dsl
|
||||
animation campfire = fire(200, 2s)
|
||||
animation torch = fire(255, 500ms)
|
||||
```
|
||||
|
||||
### Sparkle Effects
|
||||
|
||||
```berry
|
||||
def sparkles(engine, color, density, speed)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = color
|
||||
anim.density = density
|
||||
anim.speed = speed
|
||||
return anim
|
||||
end
|
||||
|
||||
animation.register_user_function("sparkles", sparkles)
|
||||
```
|
||||
|
||||
```dsl
|
||||
animation stars = sparkles(white, 12, 300ms)
|
||||
animation fairy_dust = sparkles(#FFD700, 8, 500ms)
|
||||
```
|
||||
|
||||
### Position-Based Effects
|
||||
|
||||
```berry
|
||||
def pulse_at(engine, color, position, width, speed)
|
||||
var anim = animation.beacon_animation(engine)
|
||||
anim.color = color
|
||||
anim.position = position
|
||||
anim.width = width
|
||||
anim.period = speed
|
||||
return anim
|
||||
end
|
||||
|
||||
animation.register_user_function("pulse_at", pulse_at)
|
||||
```
|
||||
|
||||
```dsl
|
||||
animation left_pulse = pulse_at(green, 5, 3, 2s)
|
||||
animation right_pulse = pulse_at(blue, 25, 3, 2s)
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Multi-Layer Effects
|
||||
|
||||
```berry
|
||||
def rainbow_sparkle(engine, base_speed, sparkle_density)
|
||||
# Create base rainbow animation
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = base_speed
|
||||
|
||||
var base_anim = animation.filled(engine)
|
||||
base_anim.color_provider = rainbow_provider
|
||||
base_anim.priority = 1
|
||||
|
||||
# Note: This is a simplified example
|
||||
# Real multi-layer effects would require engine support
|
||||
return base_anim
|
||||
end
|
||||
|
||||
animation.register_user_function("rainbow_sparkle", rainbow_sparkle)
|
||||
```
|
||||
|
||||
### Preset Configurations
|
||||
|
||||
```berry
|
||||
def police_lights(engine, flash_speed)
|
||||
var anim = animation.pulsating_animation(engine)
|
||||
anim.color = 0xFFFF0000 # Red
|
||||
anim.min_brightness = 0
|
||||
anim.max_brightness = 255
|
||||
anim.period = flash_speed
|
||||
return anim
|
||||
end
|
||||
|
||||
def warning_strobe(engine)
|
||||
return police_lights(engine, 200) # Fast strobe
|
||||
end
|
||||
|
||||
def gentle_alert(engine)
|
||||
return police_lights(engine, 1000) # Slow pulse
|
||||
end
|
||||
|
||||
animation.register_user_function("police", police_lights)
|
||||
animation.register_user_function("strobe", warning_strobe)
|
||||
animation.register_user_function("alert", gentle_alert)
|
||||
```
|
||||
|
||||
```dsl
|
||||
animation emergency = strobe()
|
||||
animation notification = alert()
|
||||
animation custom_police = police(500ms)
|
||||
```
|
||||
|
||||
## Function Organization
|
||||
|
||||
### Single File Approach
|
||||
|
||||
```berry
|
||||
# user_animations.be
|
||||
import animation
|
||||
|
||||
def breathing(engine, color, period)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
def fire_effect(engine, intensity, speed)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
def sparkle_effect(engine, color, density, speed)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
# Register all functions
|
||||
animation.register_user_function("breathing", breathing)
|
||||
animation.register_user_function("fire", fire_effect)
|
||||
animation.register_user_function("sparkle", sparkle_effect)
|
||||
|
||||
print("Custom animations loaded!")
|
||||
```
|
||||
|
||||
### Modular Approach
|
||||
|
||||
```berry
|
||||
# animations/fire.be
|
||||
def fire_effect(engine, intensity, speed)
|
||||
# ... implementation
|
||||
end
|
||||
|
||||
def torch_effect(engine)
|
||||
return fire_effect(engine, 255, 500)
|
||||
end
|
||||
|
||||
return {
|
||||
'fire': fire_effect,
|
||||
'torch': torch_effect
|
||||
}
|
||||
```
|
||||
|
||||
```berry
|
||||
# main.be
|
||||
import animation
|
||||
|
||||
# Register functions
|
||||
animation.register_user_function("fire", fire_effects['fire'])
|
||||
animation.register_user_function("torch", fire_effects['torch'])
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Function Design
|
||||
|
||||
1. **Use descriptive names**: `breathing_slow` not `bs`
|
||||
2. **Logical parameter order**: color first, then timing, then modifiers
|
||||
3. **Sensible defaults**: Make functions work with minimal parameters
|
||||
4. **Return animations**: Always return a configured animation object
|
||||
|
||||
### Parameter Handling
|
||||
|
||||
```berry
|
||||
def flexible_pulse(engine, color, period, min_brightness, max_brightness)
|
||||
# Provide defaults for optional parameters
|
||||
if min_brightness == nil min_brightness = 50 end
|
||||
if max_brightness == nil max_brightness = 255 end
|
||||
|
||||
var anim = animation.pulsating_animation(engine)
|
||||
anim.color = color
|
||||
anim.period = period
|
||||
anim.min_brightness = min_brightness
|
||||
anim.max_brightness = max_brightness
|
||||
return anim
|
||||
end
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```berry
|
||||
def safe_comet(engine, color, tail_length, speed)
|
||||
# Validate parameters
|
||||
if tail_length < 1 tail_length = 1 end
|
||||
if tail_length > 20 tail_length = 20 end
|
||||
if speed < 100 speed = 100 end
|
||||
|
||||
var anim = animation.comet_animation(engine)
|
||||
anim.color = color
|
||||
anim.tail_length = tail_length
|
||||
anim.speed = speed
|
||||
return anim
|
||||
end
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
```berry
|
||||
# Creates a pulsing animation with customizable brightness range
|
||||
# Parameters:
|
||||
# color: The color to pulse (hex or named color)
|
||||
# period: How long one pulse cycle takes (in milliseconds)
|
||||
# min_brightness: Minimum brightness (0-255, default: 50)
|
||||
# max_brightness: Maximum brightness (0-255, default: 255)
|
||||
# Returns: Configured pulse animation
|
||||
def breathing_effect(engine, color, period, min_brightness, max_brightness)
|
||||
# ... implementation
|
||||
end
|
||||
```
|
||||
|
||||
## Loading and Using Functions
|
||||
|
||||
### In Tasmota autoexec.be
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
# Load your custom functions
|
||||
load("user_animations.be")
|
||||
|
||||
# Now they're available in DSL
|
||||
var dsl_code = '''
|
||||
animation my_fire = fire(200, 1500ms)
|
||||
animation my_sparkles = sparkle(white, 8, 400ms)
|
||||
|
||||
sequence show {
|
||||
play my_fire for 10s
|
||||
play my_sparkles for 5s
|
||||
}
|
||||
|
||||
run show
|
||||
'''
|
||||
|
||||
animation_dsl.execute(dsl_code)
|
||||
```
|
||||
|
||||
### From Files
|
||||
|
||||
```berry
|
||||
# Save DSL with custom functions
|
||||
var my_show = '''
|
||||
animation campfire = fire(180, 2s)
|
||||
animation stars = sparkle(#FFFFFF, 6, 600ms)
|
||||
|
||||
sequence night_scene {
|
||||
play campfire for 30s
|
||||
play stars for 10s
|
||||
}
|
||||
|
||||
run night_scene
|
||||
'''
|
||||
|
||||
# Save to file
|
||||
var f = open("night_scene.anim", "w")
|
||||
f.write(my_show)
|
||||
f.close()
|
||||
|
||||
# Load and run
|
||||
animation_dsl.load_file("night_scene.anim")
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Function Signature Requirements
|
||||
|
||||
User functions must follow this exact pattern:
|
||||
|
||||
```berry
|
||||
def function_name(engine, param1, param2, ...)
|
||||
# engine is ALWAYS the first parameter
|
||||
# followed by user-provided parameters
|
||||
return animation_object
|
||||
end
|
||||
```
|
||||
|
||||
### How the DSL Transpiler Works
|
||||
|
||||
When you write DSL like this:
|
||||
```dsl
|
||||
animation my_anim = my_function(arg1, arg2)
|
||||
```
|
||||
|
||||
The transpiler generates Berry code like this:
|
||||
```berry
|
||||
var my_anim_ = animation.get_user_function('my_function')(engine, arg1, arg2)
|
||||
```
|
||||
|
||||
The `engine` parameter is automatically inserted as the first argument.
|
||||
|
||||
### Registration API
|
||||
|
||||
```berry
|
||||
# Register a function
|
||||
animation.register_user_function(name, function)
|
||||
|
||||
# Check if a function is registered
|
||||
if animation.is_user_function("my_function")
|
||||
print("Function is registered")
|
||||
end
|
||||
|
||||
# Get a registered function
|
||||
var func = animation.get_user_function("my_function")
|
||||
|
||||
# List all registered functions
|
||||
var functions = animation.list_user_functions()
|
||||
for name : functions
|
||||
print("Registered:", name)
|
||||
end
|
||||
```
|
||||
|
||||
### Engine Parameter
|
||||
|
||||
The `engine` parameter provides:
|
||||
- Access to the LED strip: `engine.get_strip_length()`
|
||||
- Current time: `engine.time_ms`
|
||||
- Animation management context
|
||||
|
||||
Always use the provided engine when creating animations - don't create your own engine instances.
|
||||
|
||||
### Return Value Requirements
|
||||
|
||||
User functions must return an animation object that:
|
||||
- Extends `animation.animation` or `animation.pattern`
|
||||
- Is properly configured with the engine
|
||||
- Has all required parameters set
|
||||
|
||||
### Error Handling
|
||||
|
||||
The framework handles errors gracefully:
|
||||
- Invalid function names are caught at DSL compile time
|
||||
- Runtime errors in user functions are reported with context
|
||||
- Failed function calls don't crash the animation system
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Function Not Found
|
||||
```
|
||||
Error: Unknown function 'my_function'
|
||||
```
|
||||
- Ensure the function is registered with `animation.register_user_function()`
|
||||
- Check that registration happens before DSL compilation
|
||||
- Verify the function name matches exactly (case-sensitive)
|
||||
|
||||
### Wrong Number of Arguments
|
||||
```
|
||||
Error: Function call failed
|
||||
```
|
||||
- Check that your function signature matches the DSL call
|
||||
- Remember that `engine` is automatically added as the first parameter
|
||||
- Verify all required parameters are provided in the DSL
|
||||
|
||||
### Animation Not Working
|
||||
- Ensure your function returns a valid animation object
|
||||
- Check that the animation is properly configured
|
||||
- Verify that the engine parameter is used correctly
|
||||
|
||||
User-defined functions provide a powerful way to extend the Animation DSL with custom effects while maintaining the clean, declarative syntax that makes the DSL easy to use.
|
||||
@ -1,58 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the Berry Animation Framework will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.1.0] - 2025-07-31
|
||||
|
||||
### Added
|
||||
- Initial public release of the Berry Animation Framework
|
||||
- Numeric version system (0xAABBCCDD format) with string conversion function
|
||||
- Core animation engine with unified architecture
|
||||
- 21 built-in animation effects including:
|
||||
- Basic animations (pulse, breathe, filled, comet, twinkle)
|
||||
- Position-based animations (pulse_position, crenel_position)
|
||||
- Advanced pattern animations (noise, plasma, sparkle, wave)
|
||||
- Motion effect animations (shift, bounce, scale, jitter)
|
||||
- Fire and gradient effects
|
||||
- Comprehensive DSL (Domain Specific Language) system
|
||||
- Value provider system for dynamic parameters
|
||||
- Color provider system with palette support
|
||||
- Event system for animation coordination
|
||||
- Sequence manager for complex animation sequences
|
||||
- 52 comprehensive test files with 100% pass rate
|
||||
- 60+ example and demo files
|
||||
- Complete API documentation
|
||||
- Performance optimizations for embedded systems
|
||||
- MIT License
|
||||
|
||||
### Features
|
||||
- **157 Berry files** with modular architecture
|
||||
- **Integer-only arithmetic** for embedded performance
|
||||
- **Memory-efficient** frame buffer management
|
||||
- **Extensible plugin system** for custom animations
|
||||
- **Fast loop integration** with Tasmota
|
||||
- **Parameter validation** and runtime safety
|
||||
- **Comprehensive error handling** with proper exceptions
|
||||
- **Cross-platform compatibility** (Tasmota/Berry environments)
|
||||
|
||||
### Documentation
|
||||
- Complete API reference with examples
|
||||
- Advanced patterns implementation guide
|
||||
- Motion effects documentation
|
||||
- DSL syntax reference and grammar specification
|
||||
- Value provider system documentation
|
||||
- Performance tips and troubleshooting guides
|
||||
- Migration guides and architecture documentation
|
||||
|
||||
### Testing
|
||||
- 52 test files covering all components
|
||||
- Unit tests for individual animations
|
||||
- Integration tests for engine and DSL
|
||||
- Performance and stress tests
|
||||
- Error handling and edge case tests
|
||||
- 100% test pass rate
|
||||
|
||||
[0.1.0]: https://github.com/your-repo/berry-animation-framework/releases/tag/v0.1.0
|
||||
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Berry Animation Framework Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@ -1,215 +0,0 @@
|
||||
# Tasmota Berry Animation Framework
|
||||
|
||||
A powerful, lightweight animation framework for controlling addressable LED strips in Tasmota using Berry scripting language.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🎨 Rich Animation Effects** - Pulse, breathe, fire, comet, twinkle, and more
|
||||
- **🌈 Advanced Color System** - Palettes, gradients, color cycling with smooth transitions
|
||||
- **📝 Domain-Specific Language (DSL)** - Write animations in intuitive, declarative syntax
|
||||
- **⚡ High Performance** - Optimized for embedded systems with minimal memory usage
|
||||
- **🔧 Extensible** - Create custom animations and user-defined functions
|
||||
- **🎯 Position-Based Effects** - Precise control over individual LED positions
|
||||
- **📊 Dynamic Parameters** - Animate colors, positions, sizes with value providers
|
||||
- **🎭 Event System** - Responsive animations that react to button presses, timers, and sensors
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. Basic Berry Animation
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
# Create LED strip (60 LEDs)
|
||||
var strip = Leds(60)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create a pulsing red animation
|
||||
var pulse_red = animation.pulse(
|
||||
animation.solid(0xFFFF0000), # Red color
|
||||
2000, # 2 second period
|
||||
50, # Min brightness (0-255)
|
||||
255 # Max brightness (0-255)
|
||||
)
|
||||
|
||||
# Start the animation
|
||||
engine.add_animation(pulse_red)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
### 2. Using the Animation DSL
|
||||
|
||||
Create a file `my_animation.anim`:
|
||||
|
||||
```dsl
|
||||
# Define colors
|
||||
color red = #FF0000
|
||||
color blue = #0000FF
|
||||
|
||||
# Create animations
|
||||
animation pulse_red = pulse(solid(red), 2s, 20%, 100%)
|
||||
animation pulse_blue = pulse(solid(blue), 3s, 30%, 100%)
|
||||
|
||||
# Create a sequence
|
||||
sequence demo {
|
||||
play pulse_red for 5s
|
||||
wait 1s
|
||||
play pulse_blue for 5s
|
||||
repeat 3 times:
|
||||
play pulse_red for 2s
|
||||
play pulse_blue for 2s
|
||||
}
|
||||
|
||||
run demo
|
||||
```
|
||||
|
||||
Load and run the DSL:
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
var strip = Leds(60)
|
||||
var runtime = animation.DSLRuntime(animation.create_engine(strip))
|
||||
runtime.load_dsl_file("my_animation.anim")
|
||||
```
|
||||
|
||||
### 3. Palette-Based Animations
|
||||
|
||||
```dsl
|
||||
# Define a fire palette
|
||||
palette fire_colors = [
|
||||
(0, #000000), # Black
|
||||
(64, #800000), # Dark red
|
||||
(128, #FF0000), # Red
|
||||
(192, #FF8000), # Orange
|
||||
(255, #FFFF00) # Yellow
|
||||
]
|
||||
|
||||
# Create fire animation
|
||||
animation fire_effect = rich_palette_animation(fire_colors, 3s, smooth, 255)
|
||||
|
||||
run fire_effect
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### User Guides
|
||||
- **[Quick Start Guide](docs/QUICK_START.md)** - Get up and running in 5 minutes
|
||||
- **[API Reference](docs/API_REFERENCE.md)** - Complete Berry API documentation
|
||||
- **[Examples](docs/EXAMPLES.md)** - Curated examples with explanations
|
||||
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions
|
||||
|
||||
### DSL (Domain-Specific Language)
|
||||
- **[DSL Reference](.kiro/specs/berry-animation-framework/dsl-specification.md)** - Complete DSL syntax guide
|
||||
- **[DSL Grammar](.kiro/specs/berry-animation-framework/dsl-grammar.md)** - Formal grammar specification
|
||||
- **[Palette Guide](.kiro/specs/berry-animation-framework/palette-quick-reference.md)** - Working with color palettes
|
||||
|
||||
### Advanced Topics
|
||||
- **[User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md)** - Create custom animation functions
|
||||
- **[Event System](.kiro/specs/berry-animation-framework/EVENT_SYSTEM.md)** - Responsive, interactive animations
|
||||
- **[Project Structure](docs/PROJECT_STRUCTURE.md)** - Navigate the codebase
|
||||
|
||||
### Framework Design
|
||||
- **[Requirements](.kiro/specs/berry-animation-framework/requirements.md)** - Project goals and requirements (✅ Complete)
|
||||
- **[Architecture](.kiro/specs/berry-animation-framework/design.md)** - Framework design and architecture
|
||||
- **[Future Features](.kiro/specs/berry-animation-framework/future_features.md)** - Planned enhancements
|
||||
|
||||
## 🎯 Core Concepts
|
||||
|
||||
### Unified Architecture
|
||||
The framework uses a **unified pattern-animation architecture** where `Animation` extends `Pattern`. This means:
|
||||
- Animations ARE patterns with temporal behavior
|
||||
- Infinite composition: animations can use other animations as base patterns
|
||||
- Consistent API across all visual elements
|
||||
|
||||
### Animation Engine
|
||||
The `AnimationEngine` is the heart of the framework:
|
||||
- Manages multiple animations with priority-based layering
|
||||
- Handles timing, blending, and LED output
|
||||
- Integrates with Tasmota's `fast_loop` for smooth performance
|
||||
|
||||
### Value Providers
|
||||
Dynamic parameters that change over time:
|
||||
- **Static values**: `solid(red)`
|
||||
- **Oscillators**: `pulse(solid(red), smooth(50, 255, 2s))`
|
||||
- **Color providers**: `rich_palette_animation(fire_palette, 3s)`
|
||||
|
||||
## 🎨 Animation Types
|
||||
|
||||
### Basic Animations
|
||||
- **`solid(color)`** - Static color fill
|
||||
- **`pulse(pattern, period, min, max)`** - Pulsing brightness
|
||||
- **`breathe(color, period)`** - Smooth breathing effect
|
||||
|
||||
### Pattern-Based Animations
|
||||
- **`rich_palette_animation(palette, period, easing, brightness)`** - Palette color cycling
|
||||
- **`gradient(color1, color2, ...)`** - Color gradients
|
||||
- **`fire_animation(intensity, speed)`** - Realistic fire simulation
|
||||
|
||||
### Position-Based Animations
|
||||
- **`pulse_position_animation(color, pos, size, fade)`** - Localized pulse
|
||||
- **`comet_animation(color, tail_length, speed)`** - Moving comet effect
|
||||
- **`twinkle_animation(color, density, speed)`** - Twinkling stars
|
||||
|
||||
### Motion Effects
|
||||
- **`shift_left(pattern, speed)`** - Move pattern left
|
||||
- **`shift_right(pattern, speed)`** - Move pattern right
|
||||
- **`bounce(pattern, period)`** - Bouncing motion
|
||||
|
||||
## 🔧 Installation
|
||||
|
||||
### For Tasmota Development
|
||||
1. Copy the `lib/libesp32/berry_animation/` directory to your Tasmota build
|
||||
2. The framework will be available as the `animation` module
|
||||
3. Use `import animation` in your Berry scripts
|
||||
|
||||
### For Testing/Development
|
||||
1. Install Berry interpreter with Tasmota extensions
|
||||
2. Set module path: `berry -m lib/libesp32/berry_animation`
|
||||
3. Run examples: `berry examples/simple_engine_test.be`
|
||||
|
||||
## 🧪 Examples
|
||||
|
||||
The framework includes comprehensive examples:
|
||||
|
||||
### Berry Examples
|
||||
- **[Basic Engine](lib/libesp32/berry_animation/examples/simple_engine_test.be)** - Simple animation setup
|
||||
- **[Color Providers](lib/libesp32/berry_animation/examples/color_provider_demo.be)** - Dynamic color effects
|
||||
- **[Position Effects](lib/libesp32/berry_animation/examples/pulse_position_animation_demo.be)** - Localized animations
|
||||
- **[Event System](lib/libesp32/berry_animation/examples/event_system_demo.be)** - Interactive animations
|
||||
|
||||
### DSL Examples
|
||||
- **[Aurora Borealis](anim_examples/aurora_borealis.anim)** - Northern lights effect
|
||||
- **[Breathing Colors](anim_examples/breathing_colors.anim)** - Smooth color breathing
|
||||
- **[Fire Effect](anim_examples/fire_demo.anim)** - Realistic fire simulation
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Run all tests
|
||||
berry lib/libesp32/berry_animation/tests/test_all.be
|
||||
|
||||
# Run specific test
|
||||
berry lib/libesp32/berry_animation/tests/animation_engine_test.be
|
||||
```
|
||||
|
||||
### Code Style
|
||||
- Follow Berry language conventions
|
||||
- Use descriptive variable names
|
||||
- Include comprehensive comments
|
||||
- Add test coverage for new features
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is part of the Tasmota ecosystem and follows the same licensing terms.
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
- **Tasmota Team** - For the excellent IoT platform
|
||||
- **Berry Language** - For the lightweight scripting language
|
||||
- **Community Contributors** - For testing, feedback, and improvements
|
||||
|
||||
---
|
||||
|
||||
**Ready to create amazing LED animations?** Start with the [Quick Start Guide](docs/QUICK_START.md)!
|
||||
@ -4,7 +4,6 @@
|
||||
# into a unified "animation" object for use in Tasmota LED strip control.
|
||||
#
|
||||
# The framework provides:
|
||||
# - Unified Pattern-Animation architecture (Animation extends Pattern)
|
||||
# - DSL (Domain Specific Language) for declarative animation definitions
|
||||
# - Value providers for dynamic parameters (oscillators, color providers)
|
||||
# - Event system for interactive animations
|
||||
@ -58,15 +57,15 @@ end
|
||||
# Import core framework components
|
||||
# These provide the fundamental architecture for the animation system
|
||||
|
||||
# Base class for parameter management - shared by Animation and ValueProvider
|
||||
import "core/parameterized_object" as parameterized_object
|
||||
register_to_animation(parameterized_object)
|
||||
|
||||
# Frame buffer management for LED strip pixel data
|
||||
import "core/frame_buffer" as frame_buffer
|
||||
register_to_animation(frame_buffer)
|
||||
|
||||
# Base Pattern class - foundation for all visual elements
|
||||
import "core/pattern_base" as pattern_base
|
||||
register_to_animation(pattern_base)
|
||||
|
||||
# Base Animation class - extends Pattern with temporal behavior
|
||||
# Base Animation class - unified foundation for all visual elements
|
||||
import "core/animation_base" as animation_base
|
||||
register_to_animation(animation_base)
|
||||
|
||||
@ -87,57 +86,6 @@ register_to_animation(event_handler)
|
||||
import "core/user_functions" as user_functions
|
||||
register_to_animation(user_functions)
|
||||
|
||||
# Import effects
|
||||
import "effects/filled" as filled_animation
|
||||
register_to_animation(filled_animation)
|
||||
import "effects/pulse" as pulse_animation
|
||||
register_to_animation(pulse_animation)
|
||||
import "effects/pulse_position" as pulse_position_animation
|
||||
register_to_animation(pulse_position_animation)
|
||||
import "effects/crenel_position" as crenel_position_animation
|
||||
register_to_animation(crenel_position_animation)
|
||||
import "effects/breathe" as breathe_animation
|
||||
register_to_animation(breathe_animation)
|
||||
import "effects/palette_pattern" as palette_pattern_animation
|
||||
register_to_animation(palette_pattern_animation)
|
||||
import "effects/comet" as comet_animation
|
||||
register_to_animation(comet_animation)
|
||||
import "effects/fire" as fire_animation
|
||||
register_to_animation(fire_animation)
|
||||
import "effects/twinkle" as twinkle_animation
|
||||
register_to_animation(twinkle_animation)
|
||||
import "effects/gradient" as gradient_animation
|
||||
register_to_animation(gradient_animation)
|
||||
import "effects/noise" as noise_animation
|
||||
register_to_animation(noise_animation)
|
||||
import "effects/plasma" as plasma_animation
|
||||
register_to_animation(plasma_animation)
|
||||
import "effects/sparkle" as sparkle_animation
|
||||
register_to_animation(sparkle_animation)
|
||||
import "effects/wave" as wave_animation
|
||||
register_to_animation(wave_animation)
|
||||
import "effects/shift" as shift_animation
|
||||
register_to_animation(shift_animation)
|
||||
import "effects/bounce" as bounce_animation
|
||||
register_to_animation(bounce_animation)
|
||||
import "effects/scale" as scale_animation
|
||||
register_to_animation(scale_animation)
|
||||
import "effects/jitter" as jitter_animation
|
||||
register_to_animation(jitter_animation)
|
||||
|
||||
# Import palette examples
|
||||
import "effects/palettes" as palettes
|
||||
register_to_animation(palettes)
|
||||
|
||||
# Import pattern implementations
|
||||
import "patterns/solid_pattern" as solid_pattern_impl
|
||||
register_to_animation(solid_pattern_impl)
|
||||
|
||||
# Import animation implementations
|
||||
# Note: pulse_animation is already imported from effects/pulse.be
|
||||
import "effects/pattern_animation" as pattern_animation_impl
|
||||
register_to_animation(pattern_animation_impl)
|
||||
|
||||
# Import value providers
|
||||
import "providers/value_provider.be" as value_provider
|
||||
register_to_animation(value_provider)
|
||||
@ -153,20 +101,58 @@ import "providers/color_cycle_color_provider.be" as color_cycle_color_provider
|
||||
register_to_animation(color_cycle_color_provider)
|
||||
import "providers/composite_color_provider.be" as composite_color_provider
|
||||
register_to_animation(composite_color_provider)
|
||||
import "providers/solid_color_provider.be" as solid_color_provider
|
||||
register_to_animation(solid_color_provider)
|
||||
import "providers/static_color_provider.be" as static_color_provider
|
||||
register_to_animation(static_color_provider)
|
||||
import "providers/rich_palette_color_provider.be" as rich_palette_color_provider
|
||||
register_to_animation(rich_palette_color_provider)
|
||||
import "providers/breathe_color_provider.be" as breathe_color_provider
|
||||
register_to_animation(breathe_color_provider)
|
||||
|
||||
# Import DSL components
|
||||
import "dsl/token.be" as dsl_token
|
||||
register_to_animation(dsl_token)
|
||||
import "dsl/lexer.be" as dsl_lexer
|
||||
register_to_animation(dsl_lexer)
|
||||
import "dsl/transpiler.be" as dsl_transpiler
|
||||
register_to_animation(dsl_transpiler)
|
||||
import "dsl/runtime.be" as dsl_runtime
|
||||
register_to_animation(dsl_runtime)
|
||||
# Import animations
|
||||
import "animations/solid" as solid_impl
|
||||
register_to_animation(solid_impl)
|
||||
import "animations/beacon" as beacon_animation
|
||||
register_to_animation(beacon_animation)
|
||||
import "animations/crenel_position" as crenel_position_animation
|
||||
register_to_animation(crenel_position_animation)
|
||||
import "animations/breathe" as breathe_animation
|
||||
register_to_animation(breathe_animation)
|
||||
import "animations/palette_pattern" as palette_pattern_animation
|
||||
register_to_animation(palette_pattern_animation)
|
||||
import "animations/comet" as comet_animation
|
||||
register_to_animation(comet_animation)
|
||||
import "animations/fire" as fire_animation
|
||||
register_to_animation(fire_animation)
|
||||
import "animations/twinkle" as twinkle_animation
|
||||
register_to_animation(twinkle_animation)
|
||||
import "animations/gradient" as gradient_animation
|
||||
register_to_animation(gradient_animation)
|
||||
import "animations/noise" as noise_animation
|
||||
register_to_animation(noise_animation)
|
||||
import "animations/plasma" as plasma_animation
|
||||
register_to_animation(plasma_animation)
|
||||
import "animations/sparkle" as sparkle_animation
|
||||
register_to_animation(sparkle_animation)
|
||||
import "animations/wave" as wave_animation
|
||||
register_to_animation(wave_animation)
|
||||
import "animations/shift" as shift_animation
|
||||
register_to_animation(shift_animation)
|
||||
import "animations/bounce" as bounce_animation
|
||||
register_to_animation(bounce_animation)
|
||||
import "animations/scale" as scale_animation
|
||||
register_to_animation(scale_animation)
|
||||
import "animations/jitter" as jitter_animation
|
||||
register_to_animation(jitter_animation)
|
||||
|
||||
# Import palette examples
|
||||
import "animations/palettes" as palettes
|
||||
register_to_animation(palettes)
|
||||
|
||||
# Import specialized animation classes
|
||||
import "animations/rich_palette_animation" as rich_palette_animation
|
||||
register_to_animation(rich_palette_animation)
|
||||
|
||||
# DSL components are now in separate animation_dsl module
|
||||
|
||||
# Function called to initialize the `Leds` and `engine` objects
|
||||
#
|
||||
@ -185,7 +171,7 @@ end
|
||||
animation.init_strip = animation_init_strip
|
||||
|
||||
# Global variable resolver with error checking
|
||||
# Used by DSL transpiler to resolve variable names during compilation
|
||||
# Used by DSL-generated code to resolve variable names during execution
|
||||
# First checks animation module, then global scope for user-defined variables
|
||||
def animation_global(name, module_name)
|
||||
import global
|
||||
|
||||
98
lib/libesp32/berry_animation/src/animation_dsl.be
Normal file
98
lib/libesp32/berry_animation/src/animation_dsl.be
Normal file
@ -0,0 +1,98 @@
|
||||
# Berry Animation Framework - DSL Module
|
||||
#
|
||||
# This module provides Domain-Specific Language (DSL) functionality for the
|
||||
# Berry Animation Framework. It allows users to write animations using a
|
||||
# declarative syntax that gets transpiled to Berry code.
|
||||
#
|
||||
# The DSL provides:
|
||||
# - Declarative animation definitions with intuitive syntax
|
||||
# - Color and palette definitions
|
||||
# - Animation sequences and timing control
|
||||
# - Property assignments and dynamic parameters
|
||||
# - Event system integration
|
||||
# - User-defined functions
|
||||
#
|
||||
# Usage:
|
||||
# import animation_dsl
|
||||
# var berry_code = animation_dsl.compile(dsl_source)
|
||||
# animation_dsl.execute(berry_code)
|
||||
#
|
||||
|
||||
import global
|
||||
# Requires to first `import animation`
|
||||
# We don't include it to not create a closure, but use the global instead
|
||||
|
||||
# Create the DSL module and make it globally accessible
|
||||
#@ solidify:animation_dsl,weak
|
||||
var animation_dsl = module("animation_dsl")
|
||||
global.animation_dsl = animation_dsl
|
||||
|
||||
# Version information for compatibility tracking
|
||||
animation_dsl.VERSION = animation.VERSION
|
||||
|
||||
# Helper function to register all exports from imported modules into the DSL module
|
||||
def register_to_dsl(m)
|
||||
for k: m.keys()
|
||||
animation_dsl.(k) = m[k]
|
||||
end
|
||||
end
|
||||
|
||||
# Import DSL components
|
||||
import "dsl/token.be" as dsl_token
|
||||
register_to_dsl(dsl_token)
|
||||
import "dsl/lexer.be" as dsl_lexer
|
||||
register_to_dsl(dsl_lexer)
|
||||
import "dsl/transpiler.be" as dsl_transpiler
|
||||
register_to_dsl(dsl_transpiler)
|
||||
import "dsl/runtime.be" as dsl_runtime
|
||||
register_to_dsl(dsl_runtime)
|
||||
|
||||
# Main DSL compilation function
|
||||
# Compiles DSL source code to Berry code
|
||||
#
|
||||
# @param source: string - DSL source code
|
||||
# @return string - Generated Berry code
|
||||
def compile_dsl_source(source)
|
||||
return animation_dsl.compile_dsl(source)
|
||||
end
|
||||
animation_dsl.compile = compile_dsl_source
|
||||
|
||||
# Execute DSL source code
|
||||
# Compiles and executes DSL source in one step
|
||||
#
|
||||
# @param source: string - DSL source code
|
||||
# @return any - Result of execution
|
||||
def execute(source)
|
||||
var berry_code = animation_dsl.compile(source)
|
||||
var compiled_fn = compile(berry_code)
|
||||
return compiled_fn()
|
||||
end
|
||||
animation_dsl.execute = execute
|
||||
|
||||
# Load and execute DSL from file
|
||||
#
|
||||
# @param filename: string - Path to DSL file
|
||||
# @return any - Result of execution
|
||||
def load_file(filename)
|
||||
var f = open(filename, "r")
|
||||
if f == nil
|
||||
raise "io_error", f"Cannot open DSL file: {filename}"
|
||||
end
|
||||
|
||||
var source = f.read()
|
||||
f.close()
|
||||
|
||||
return animation_dsl.execute(source)
|
||||
end
|
||||
animation_dsl.load_file = load_file
|
||||
|
||||
# Create a DSL runtime instance
|
||||
#
|
||||
# @return DSLRuntime - New runtime instance
|
||||
def create_runtime(strip, debug_mode)
|
||||
var engine = animation.create_engine(strip)
|
||||
return animation_dsl.DSLRuntime(engine, debug_mode)
|
||||
end
|
||||
animation_dsl.create_runtime = create_runtime
|
||||
|
||||
return animation_dsl
|
||||
151
lib/libesp32/berry_animation/src/animations/beacon.be
Normal file
151
lib/libesp32/berry_animation/src/animations/beacon.be
Normal file
@ -0,0 +1,151 @@
|
||||
# Beacon animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a beacon effect at a specific position on the LED strip.
|
||||
# It displays a color beacon with optional slew (fade) regions on both sides.
|
||||
#
|
||||
# Beacon diagram:
|
||||
# pos (1)
|
||||
# |
|
||||
# v
|
||||
# _______
|
||||
# / \
|
||||
# _______/ \____________
|
||||
# | | | |
|
||||
# |2| 3 |2|
|
||||
#
|
||||
# 1: `pos`, start of the beacon (in pixel)
|
||||
# 2: `slew_size`, number of pixels to fade from back to fore color, can be `0`
|
||||
# 3: `beacon_size`, number of pixels of the beacon
|
||||
|
||||
#@ solidify:BeaconAnimation,weak
|
||||
class BeaconAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Parameter definitions following the new specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"pos": {"default": 0},
|
||||
"beacon_size": {"min": 0, "default": 1},
|
||||
"slew_size": {"min": 0, "default": 0}
|
||||
}
|
||||
|
||||
# Render the beacon to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
var pixel_size = frame.width
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
var slew_size = self.slew_size
|
||||
var beacon_size = self.beacon_size
|
||||
var color = self.color
|
||||
|
||||
# Fill background if not transparent
|
||||
if back_color != 0xFF000000
|
||||
frame.fill_pixels(back_color)
|
||||
end
|
||||
|
||||
# Calculate beacon boundaries
|
||||
var beacon_min = pos
|
||||
var beacon_max = pos + beacon_size
|
||||
|
||||
# Clamp to frame boundaries
|
||||
if beacon_min < 0
|
||||
beacon_min = 0
|
||||
end
|
||||
if beacon_max >= pixel_size
|
||||
beacon_max = pixel_size
|
||||
end
|
||||
|
||||
# Draw the main beacon
|
||||
var i = beacon_min
|
||||
while i < beacon_max
|
||||
frame.set_pixel_color(i, color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Draw slew regions if slew_size > 0
|
||||
if slew_size > 0
|
||||
# Left slew (fade from background to beacon color)
|
||||
var left_slew_min = pos - slew_size
|
||||
var left_slew_max = pos
|
||||
|
||||
if left_slew_min < 0
|
||||
left_slew_min = 0
|
||||
end
|
||||
if left_slew_max >= pixel_size
|
||||
left_slew_max = pixel_size
|
||||
end
|
||||
|
||||
i = left_slew_min
|
||||
while i < left_slew_max
|
||||
# Calculate blend factor (255 = background, 0 = beacon color)
|
||||
var blend_factor
|
||||
if slew_size == 1
|
||||
# For single pixel slew, use 50% blend
|
||||
blend_factor = 128
|
||||
else
|
||||
blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0)
|
||||
end
|
||||
# Create color with appropriate alpha for blending
|
||||
var alpha = 255 - blend_factor # Invert so 0 = transparent, 255 = opaque
|
||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
||||
var blended_color = frame.blend(back_color, blend_color)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Right slew (fade from beacon color to background)
|
||||
var right_slew_min = pos + beacon_size
|
||||
var right_slew_max = pos + beacon_size + slew_size
|
||||
|
||||
if right_slew_min < 0
|
||||
right_slew_min = 0
|
||||
end
|
||||
if right_slew_max >= pixel_size
|
||||
right_slew_max = pixel_size
|
||||
end
|
||||
|
||||
i = right_slew_min
|
||||
while i < right_slew_max
|
||||
# Calculate blend factor (0 = beacon color, 255 = background)
|
||||
var blend_factor
|
||||
if slew_size == 1
|
||||
# For single pixel slew, use 50% blend
|
||||
blend_factor = 128
|
||||
else
|
||||
blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255)
|
||||
end
|
||||
# Create color with appropriate alpha for blending
|
||||
var alpha = 255 - blend_factor # Start opaque, fade to transparent
|
||||
var blend_color = (alpha << 24) | (color & 0x00FFFFFF)
|
||||
var blended_color = frame.blend(back_color, blend_color)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"BeaconAnimation(color=0x{self.color :08x}, pos={self.pos}, beacon_size={self.beacon_size}, slew_size={self.slew_size})"
|
||||
end
|
||||
end
|
||||
|
||||
# Export class directly - no redundant factory function needed
|
||||
return {'beacon_animation': BeaconAnimation}
|
||||
287
lib/libesp32/berry_animation/src/animations/bounce.be
Normal file
287
lib/libesp32/berry_animation/src/animations/bounce.be
Normal file
@ -0,0 +1,287 @@
|
||||
# Bounce animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates bouncing effects where patterns bounce back and forth
|
||||
# across the LED strip with configurable physics and damping.
|
||||
|
||||
#@ solidify:BounceAnimation,weak
|
||||
class BounceAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_position # Current position in 1/256th pixels
|
||||
var current_velocity # Current velocity in 1/256th pixels per second
|
||||
var bounce_center # Center point for bouncing
|
||||
var source_frame # Frame buffer for source animation
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var last_update_time # Last update time for physics calculation
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"source_animation": {"type": "instance", "default": nil},
|
||||
"bounce_speed": {"min": 0, "max": 255, "default": 128},
|
||||
"bounce_range": {"min": 0, "max": 1000, "default": 0},
|
||||
"damping": {"min": 0, "max": 255, "default": 250},
|
||||
"gravity": {"min": 0, "max": 255, "default": 0}
|
||||
}
|
||||
|
||||
# Initialize a new Bounce animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.current_position = 0
|
||||
self.current_velocity = 0
|
||||
self.bounce_center = 0
|
||||
self.source_frame = nil
|
||||
self.current_colors = []
|
||||
self.last_update_time = 0
|
||||
|
||||
# Initialize with default strip length
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Initialize frame buffers and arrays
|
||||
def _initialize_buffers()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
self.bounce_center = current_strip_length * 256 / 2 # Center in 1/256th pixels
|
||||
self.current_position = self.bounce_center
|
||||
|
||||
# Initialize velocity based on bounce_speed
|
||||
var pixels_per_second = tasmota.scale_uint(self.bounce_speed, 0, 255, 0, 20)
|
||||
self.current_velocity = pixels_per_second * 256 # Convert to 1/256th pixels per second
|
||||
|
||||
# Initialize rendering buffers
|
||||
self.source_frame = animation.frame_buffer(current_strip_length)
|
||||
self.current_colors.resize(current_strip_length)
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Override start method for timing control and ValueProvider propagation
|
||||
def start(time_ms)
|
||||
# Call parent start first (handles ValueProvider propagation)
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Reset physics state for fresh start/restart
|
||||
var actual_start_time = time_ms != nil ? time_ms : self.engine.time_ms
|
||||
self.last_update_time = actual_start_time
|
||||
|
||||
# Reset position and velocity
|
||||
self._initialize_buffers()
|
||||
|
||||
# Start source animation if it exists
|
||||
var current_source = self.source_animation
|
||||
if current_source != nil
|
||||
current_source.start(actual_start_time)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "bounce_speed"
|
||||
# Update velocity if speed changed
|
||||
var pixels_per_second = tasmota.scale_uint(value, 0, 255, 0, 20)
|
||||
var new_velocity = pixels_per_second * 256
|
||||
# Preserve direction
|
||||
if self.current_velocity < 0
|
||||
self.current_velocity = -new_velocity
|
||||
else
|
||||
self.current_velocity = new_velocity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Initialize last_update_time on first update
|
||||
if self.last_update_time == 0
|
||||
self.last_update_time = time_ms
|
||||
end
|
||||
|
||||
# Calculate time delta
|
||||
var dt = time_ms - self.last_update_time
|
||||
if dt <= 0
|
||||
return true
|
||||
end
|
||||
self.last_update_time = time_ms
|
||||
|
||||
# Update physics
|
||||
self._update_physics(dt)
|
||||
|
||||
# Update source animation if it exists
|
||||
var current_source = self.source_animation
|
||||
if current_source != nil
|
||||
if !current_source.is_running
|
||||
current_source.start(self.start_time)
|
||||
end
|
||||
current_source.update(time_ms)
|
||||
end
|
||||
|
||||
# Calculate bounced colors
|
||||
self._calculate_bounce()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update bounce physics
|
||||
def _update_physics(dt_ms)
|
||||
# Cache parameter values for performance
|
||||
var current_gravity = self.gravity
|
||||
var current_bounce_range = self.bounce_range
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var current_damping = self.damping
|
||||
|
||||
# Use integer arithmetic for physics (dt in milliseconds)
|
||||
|
||||
# Apply gravity (downward acceleration)
|
||||
if current_gravity > 0
|
||||
var gravity_accel = tasmota.scale_uint(current_gravity, 0, 255, 0, 1000) # pixels/sec²
|
||||
# Convert to 1/256th pixels per millisecond: accel * dt / 1000
|
||||
var velocity_change = gravity_accel * dt_ms / 1000
|
||||
self.current_velocity += velocity_change
|
||||
end
|
||||
|
||||
# Update position: velocity is in 1/256th pixels per second
|
||||
# Convert to position change: velocity * dt / 1000
|
||||
self.current_position += self.current_velocity * dt_ms / 1000
|
||||
|
||||
# Calculate bounce boundaries
|
||||
var effective_range = current_bounce_range > 0 ? current_bounce_range : current_strip_length
|
||||
var half_range = effective_range * 256 / 2
|
||||
var min_pos = self.bounce_center - half_range
|
||||
var max_pos = self.bounce_center + half_range
|
||||
|
||||
# Check for bounces
|
||||
var bounced = false
|
||||
if self.current_position <= min_pos
|
||||
self.current_position = min_pos
|
||||
self.current_velocity = -self.current_velocity
|
||||
bounced = true
|
||||
elif self.current_position >= max_pos
|
||||
self.current_position = max_pos
|
||||
self.current_velocity = -self.current_velocity
|
||||
bounced = true
|
||||
end
|
||||
|
||||
# Apply damping on bounce
|
||||
if bounced && current_damping < 255
|
||||
var damping_factor = tasmota.scale_uint(current_damping, 0, 255, 0, 255)
|
||||
self.current_velocity = tasmota.scale_uint(self.current_velocity, 0, 255, 0, damping_factor)
|
||||
if self.current_velocity < 0
|
||||
self.current_velocity = -tasmota.scale_uint(-self.current_velocity, 0, 255, 0, damping_factor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate bounced colors for all pixels
|
||||
def _calculate_bounce()
|
||||
# Clear source frame
|
||||
self.source_frame.clear()
|
||||
|
||||
# Render source animation to frame
|
||||
var current_source = self.source_animation
|
||||
if current_source != nil
|
||||
current_source.render(self.source_frame, 0)
|
||||
end
|
||||
|
||||
# Cache strip length for performance
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Apply bounce transformation
|
||||
var pixel_position = self.current_position / 256 # Convert to pixel units
|
||||
var offset = pixel_position - current_strip_length / 2 # Offset from center
|
||||
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
var source_pos = i - offset
|
||||
|
||||
# Clamp to strip bounds
|
||||
if source_pos >= 0 && source_pos < current_strip_length
|
||||
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
|
||||
else
|
||||
self.current_colors[i] = 0xFF000000 # Black for out-of-bounds
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render bounce to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
return f"BounceAnimation(speed={self.bounce_speed}, damping={self.damping}, gravity={self.gravity}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following parameterized class specification
|
||||
|
||||
# Create a basic bounce animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine instance
|
||||
# @return BounceAnimation - A new bounce animation instance
|
||||
def bounce_basic(engine)
|
||||
var bounce = animation.bounce_animation(engine)
|
||||
bounce.bounce_speed = 128
|
||||
bounce.bounce_range = 0 # full strip range
|
||||
bounce.damping = 250
|
||||
bounce.gravity = 0
|
||||
bounce.name = "bounce_basic"
|
||||
return bounce
|
||||
end
|
||||
|
||||
# Create a gravity bounce animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine instance
|
||||
# @return BounceAnimation - A new bounce animation instance
|
||||
def bounce_gravity(engine)
|
||||
var bounce = animation.bounce_animation(engine)
|
||||
bounce.bounce_speed = 100
|
||||
bounce.bounce_range = 0 # full strip range
|
||||
bounce.damping = 240
|
||||
bounce.gravity = 128
|
||||
bounce.name = "bounce_gravity"
|
||||
return bounce
|
||||
end
|
||||
|
||||
# Create a constrained bounce animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine instance
|
||||
# @return BounceAnimation - A new bounce animation instance
|
||||
def bounce_constrained(engine)
|
||||
var bounce = animation.bounce_animation(engine)
|
||||
bounce.bounce_speed = 150
|
||||
bounce.bounce_range = 15 # constrained range
|
||||
bounce.damping = 250
|
||||
bounce.gravity = 0
|
||||
bounce.name = "bounce_constrained"
|
||||
return bounce
|
||||
end
|
||||
|
||||
return {'bounce_animation': BounceAnimation, 'bounce_basic': bounce_basic, 'bounce_gravity': bounce_gravity, 'bounce_constrained': bounce_constrained}
|
||||
96
lib/libesp32/berry_animation/src/animations/breathe.be
Normal file
96
lib/libesp32/berry_animation/src/animations/breathe.be
Normal file
@ -0,0 +1,96 @@
|
||||
# Breathe animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a breathing/pulsing effect that oscillates between a minimum and maximum brightness.
|
||||
# It supports different curve patterns from simple sine waves to natural breathing with pauses.
|
||||
# It's useful for creating both smooth pulsing effects and calming, organic lighting effects.
|
||||
#
|
||||
# The effect uses a breathe_color_provider internally to generate the breathing color effect.
|
||||
# - curve_factor 1: Pure cosine wave (equivalent to pulse animation)
|
||||
# - curve_factor 2-5: Natural breathing with pauses at peaks (5 = most pronounced pauses)
|
||||
|
||||
#@ solidify:BreatheAnimation,weak
|
||||
class BreatheAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var breathe_provider # Internal breathe color provider
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"base_color": {"default": 0xFFFFFFFF}, # The base color to breathe (32-bit ARGB value)
|
||||
"min_brightness": {"min": 0, "max": 255, "default": 0}, # Minimum brightness level (0-255)
|
||||
"max_brightness": {"min": 0, "max": 255, "default": 255}, # Maximum brightness level (0-255)
|
||||
"period": {"min": 100, "default": 3000}, # Time for one complete breathe cycle in milliseconds
|
||||
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
|
||||
}
|
||||
|
||||
# Initialize a new Breathe animation
|
||||
# Following parameterized class specification - engine parameter only
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (required)
|
||||
def init(engine)
|
||||
# Call parent constructor with engine parameter only
|
||||
super(self).init(engine)
|
||||
|
||||
# Create internal breathe color provider
|
||||
self.breathe_provider = animation.breathe_color(engine)
|
||||
|
||||
# Set the animation's color parameter to use the breathe provider
|
||||
self.color = self.breathe_provider
|
||||
end
|
||||
|
||||
# Handle parameter changes - propagate to internal breathe provider
|
||||
def on_param_changed(name, value)
|
||||
# Propagate relevant parameters to the breathe provider
|
||||
if name == "base_color"
|
||||
self.breathe_provider.base_color = value
|
||||
elif name == "min_brightness"
|
||||
self.breathe_provider.min_brightness = value
|
||||
elif name == "max_brightness"
|
||||
self.breathe_provider.max_brightness = value
|
||||
elif name == "period"
|
||||
self.breathe_provider.duration = value
|
||||
elif name == "curve_factor"
|
||||
self.breathe_provider.curve_factor = value
|
||||
end
|
||||
end
|
||||
|
||||
# Override start method to synchronize the internal provider
|
||||
#
|
||||
# @param start_time: int - Optional start time in milliseconds
|
||||
# @return self for method chaining
|
||||
def start(start_time)
|
||||
# Call parent start method first
|
||||
super(self).start(start_time)
|
||||
|
||||
# # Synchronize the breathe provider with current parameters
|
||||
# self.breathe_provider.base_color = self.base_color
|
||||
# self.breathe_provider.min_brightness = self.min_brightness
|
||||
# self.breathe_provider.max_brightness = self.max_brightness
|
||||
# self.breathe_provider.duration = self.period
|
||||
# self.breathe_provider.curve_factor = self.curve_factor
|
||||
|
||||
# Start the breathe provider with the same time
|
||||
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
|
||||
self.breathe_provider.start(actual_start_time)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# The render method is inherited from Animation base class
|
||||
# It automatically uses self.color (which is set to self.breathe_provider)
|
||||
# The breathe_provider produces the breathing color effect
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"BreatheAnimation(base_color=0x{self.base_color :08x}, min_brightness={self.min_brightness}, max_brightness={self.max_brightness}, period={self.period}, curve_factor={self.curve_factor}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory method to create a pulsating animation (sine wave, equivalent to old pulse.be)
|
||||
def pulsating_animation(engine)
|
||||
var anim = animation.breathe_animation(engine)
|
||||
anim.curve_factor = 1 # Pure sine wave for pulsing effect
|
||||
anim.period = 1000 # Faster default period for pulsing
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'breathe_animation': BreatheAnimation, 'pulsating_animation': pulsating_animation}
|
||||
195
lib/libesp32/berry_animation/src/animations/comet.be
Normal file
195
lib/libesp32/berry_animation/src/animations/comet.be
Normal file
@ -0,0 +1,195 @@
|
||||
# Comet animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a comet effect with a bright head and a fading tail.
|
||||
# The comet moves across the LED strip with customizable speed, length, and direction.
|
||||
#
|
||||
# The comet uses sub-pixel positioning (1/256th pixels) for smooth movement and supports
|
||||
# both wrapping around the strip and bouncing off the ends.
|
||||
|
||||
#@ solidify:CometAnimation,weak
|
||||
class CometAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var head_position # Current position of the comet head (in 1/256th pixels for smooth movement)
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF}, # Color for the comet head (32-bit ARGB value)
|
||||
"tail_length": {"min": 1, "max": 50, "default": 5}, # Length of the comet tail in pixels
|
||||
"speed": {"min": 1, "max": 25600, "default": 2560}, # Movement speed in 1/256th pixels per second
|
||||
"direction": {"enum": [-1, 1], "default": 1}, # Direction of movement (1 = forward, -1 = backward)
|
||||
"wrap_around": {"min": 0, "max": 1, "default": 1}, # Whether comet wraps around the strip (bool)
|
||||
"fade_factor": {"min": 0, "max": 255, "default": 179} # How quickly the tail fades (0-255, 255 = no fade)
|
||||
}
|
||||
|
||||
# Initialize a new Comet animation
|
||||
# Following parameterized class specification - engine parameter only
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (required)
|
||||
def init(engine)
|
||||
# Call parent constructor with engine parameter only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
# Initialize position based on default direction (forward = start at beginning)
|
||||
self.head_position = 0
|
||||
end
|
||||
|
||||
# Handle parameter changes - reset position when direction changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "direction"
|
||||
# Reset position when direction changes
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
if value > 0
|
||||
self.head_position = 0 # Start at beginning for forward movement
|
||||
else
|
||||
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Cache parameter values for performance (read once, use multiple times)
|
||||
var current_speed = self.speed
|
||||
var current_direction = self.direction
|
||||
var current_wrap_around = self.wrap_around
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
# Calculate movement based on elapsed time and speed
|
||||
# speed is in 1/256th pixels per second, elapsed is in milliseconds
|
||||
# distance = (speed * elapsed_ms) / 1000
|
||||
var distance_moved = (current_speed * elapsed * current_direction) / 1000
|
||||
|
||||
# Update head position
|
||||
if current_direction > 0
|
||||
self.head_position = distance_moved
|
||||
else
|
||||
self.head_position = ((strip_length - 1) * 256) + distance_moved
|
||||
end
|
||||
|
||||
# Handle wrapping or bouncing (convert to pixel boundaries)
|
||||
var strip_length_subpixels = strip_length * 256
|
||||
if current_wrap_around != 0
|
||||
# Wrap around the strip
|
||||
while self.head_position >= strip_length_subpixels
|
||||
self.head_position -= strip_length_subpixels
|
||||
end
|
||||
while self.head_position < 0
|
||||
self.head_position += strip_length_subpixels
|
||||
end
|
||||
else
|
||||
# Bounce off the ends
|
||||
if self.head_position >= strip_length_subpixels
|
||||
self.head_position = (strip_length - 1) * 256
|
||||
# Update direction parameter using virtual member assignment
|
||||
self.direction = -current_direction
|
||||
elif self.head_position < 0
|
||||
self.head_position = 0
|
||||
# Update direction parameter using virtual member assignment
|
||||
self.direction = -current_direction
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the comet to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Get the integer position of the head (convert from 1/256th pixels to pixels)
|
||||
var head_pixel = self.head_position / 256
|
||||
|
||||
# Get current parameter values using virtual member access (resolves ValueProviders automatically)
|
||||
var current_color = self.color
|
||||
var tail_length = self.tail_length
|
||||
var direction = self.direction
|
||||
var wrap_around = self.wrap_around
|
||||
var fade_factor = self.fade_factor
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Extract color components from current color (ARGB format)
|
||||
var head_a = (current_color >> 24) & 0xFF
|
||||
var head_r = (current_color >> 16) & 0xFF
|
||||
var head_g = (current_color >> 8) & 0xFF
|
||||
var head_b = current_color & 0xFF
|
||||
|
||||
# Render the comet head and tail
|
||||
var i = 0
|
||||
while i < tail_length
|
||||
var pixel_pos = head_pixel - (i * direction)
|
||||
|
||||
# Handle wrapping for pixel position
|
||||
if wrap_around != 0
|
||||
while pixel_pos >= strip_length
|
||||
pixel_pos -= strip_length
|
||||
end
|
||||
while pixel_pos < 0
|
||||
pixel_pos += strip_length
|
||||
end
|
||||
else
|
||||
# Skip pixels outside the strip
|
||||
if pixel_pos < 0 || pixel_pos >= strip_length
|
||||
i += 1
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate alpha based on distance from head (alpha-based fading)
|
||||
var alpha = 255 # Start at full alpha for head
|
||||
if i > 0
|
||||
# Use fade_factor to calculate exponential alpha decay
|
||||
var j = 0
|
||||
while j < i
|
||||
alpha = tasmota.scale_uint(alpha, 0, 255, 0, fade_factor)
|
||||
j += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Keep RGB components at full brightness, only fade via alpha
|
||||
# This creates a more realistic comet tail that fades to transparent
|
||||
var pixel_color = (alpha << 24) | (head_r << 16) | (head_g << 8) | head_b
|
||||
|
||||
# Set the pixel in the frame buffer
|
||||
if pixel_pos >= 0 && pixel_pos < frame.width
|
||||
frame.set_pixel_color(pixel_pos, pixel_color)
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
var color_str
|
||||
if animation.is_value_provider(self.color)
|
||||
color_str = str(self.color)
|
||||
else
|
||||
color_str = f"0x{self.color :08x}"
|
||||
end
|
||||
return f"CometAnimation(color={color_str}, head_pos={self.head_position / 256:.1f}, tail_length={self.tail_length}, speed={self.speed}, direction={self.direction}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
return {'comet_animation': CometAnimation}
|
||||
132
lib/libesp32/berry_animation/src/animations/crenel_position.be
Normal file
132
lib/libesp32/berry_animation/src/animations/crenel_position.be
Normal file
@ -0,0 +1,132 @@
|
||||
# Crenel Position animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a crenel (square wave) effect at a specific position on the LED strip.
|
||||
# It displays repeating rectangular pulses with configurable spacing and count.
|
||||
#
|
||||
# Crenel diagram:
|
||||
# pos (1)
|
||||
# |
|
||||
# v (*4)
|
||||
# ______ ____
|
||||
# | | |
|
||||
# _________| |_________|
|
||||
#
|
||||
# | 2 | 3 |
|
||||
#
|
||||
# 1: `pos`, start of the pulse (in pixel)
|
||||
# 2: `pulse_size`, number of pixels of the pulse
|
||||
# 3: `low_size`, number of pixel until next pos - full cycle is 2 + 3
|
||||
# 4: `nb_pulse`, number of pulses, or `-1` for infinite
|
||||
|
||||
#@ solidify:CrenelPositionAnimation,weak
|
||||
class CrenelPositionAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Parameter definitions with constraints
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"pos": {"default": 0},
|
||||
"pulse_size": {"min": 0, "default": 1},
|
||||
"low_size": {"min": 0, "default": 3},
|
||||
"nb_pulse": {"default": -1}
|
||||
}
|
||||
|
||||
# Render the crenel pattern to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
var pixel_size = frame.width
|
||||
|
||||
# Access parameters via virtual members (automatically resolves ValueProviders)
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
var pulse_size = self.pulse_size
|
||||
var low_size = self.low_size
|
||||
var nb_pulse = self.nb_pulse
|
||||
var color = self.color
|
||||
|
||||
var period = int(pulse_size + low_size)
|
||||
|
||||
# Fill background if not transparent
|
||||
if back_color != 0xFF000000
|
||||
frame.fill_pixels(back_color)
|
||||
end
|
||||
|
||||
# Ensure we have a meaningful period
|
||||
if period <= 0
|
||||
period = 1
|
||||
end
|
||||
|
||||
# Nothing to paint if nb_pulse is 0
|
||||
if nb_pulse == 0
|
||||
return true
|
||||
end
|
||||
|
||||
# For infinite pulses, optimize starting position
|
||||
if nb_pulse < 0
|
||||
# Find the position of the first visible falling range (pos + pulse_size - 1)
|
||||
pos = ((pos + pulse_size - 1) % period) - pulse_size + 1
|
||||
else
|
||||
# For finite pulses, skip periods that are completely before the visible area
|
||||
while (pos < -period) && (nb_pulse != 0)
|
||||
pos += period
|
||||
nb_pulse -= 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render pulses
|
||||
while (pos < pixel_size) && (nb_pulse != 0)
|
||||
var i = 0
|
||||
if pos < 0
|
||||
i = -pos
|
||||
end
|
||||
# Invariant: pos + i >= 0
|
||||
|
||||
# Draw the pulse pixels
|
||||
while (i < pulse_size) && (pos + i < pixel_size)
|
||||
frame.set_pixel_color(pos + i, color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Move to next pulse position
|
||||
pos += period
|
||||
nb_pulse -= 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# NO setter/getter methods - use direct assignment instead:
|
||||
# obj.color = value
|
||||
# obj.back_color = value
|
||||
# obj.pos = value
|
||||
# obj.pulse_size = value
|
||||
# obj.low_size = value
|
||||
# obj.nb_pulse = value
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
var color_str
|
||||
var raw_color = self.get_param("color")
|
||||
if animation.is_value_provider(raw_color)
|
||||
color_str = str(raw_color)
|
||||
else
|
||||
color_str = f"0x{self.color :08x}"
|
||||
end
|
||||
return f"CrenelPositionAnimation(color={color_str}, pos={self.pos}, pulse_size={self.pulse_size}, low_size={self.low_size}, nb_pulse={self.nb_pulse}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
return {'crenel_position_animation': CrenelPositionAnimation}
|
||||
263
lib/libesp32/berry_animation/src/animations/fire.be
Normal file
263
lib/libesp32/berry_animation/src/animations/fire.be
Normal file
@ -0,0 +1,263 @@
|
||||
# Fire animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a realistic fire effect with flickering flames.
|
||||
# The fire uses random intensity variations and warm colors to simulate flames.
|
||||
|
||||
#@ solidify:FireAnimation,weak
|
||||
class FireAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var heat_map # Array storing heat values for each pixel (0-255)
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var last_update # Last update time for flicker timing
|
||||
var random_seed # Seed for random number generation
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": nil},
|
||||
"intensity": {"min": 0, "max": 255, "default": 180},
|
||||
"flicker_speed": {"min": 1, "max": 20, "default": 8},
|
||||
"flicker_amount": {"min": 0, "max": 255, "default": 100},
|
||||
"cooling_rate": {"min": 0, "max": 255, "default": 55},
|
||||
"sparking_rate": {"min": 0, "max": 255, "default": 120}
|
||||
}
|
||||
|
||||
# Initialize a new Fire animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (required)
|
||||
def init(engine)
|
||||
# Call parent constructor with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.heat_map = []
|
||||
self.current_colors = []
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize random seed using engine time
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
end
|
||||
|
||||
# Initialize buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.heat_map.resize(strip_length)
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Initialize all pixels to zero heat
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.heat_map[i] = 0
|
||||
self.current_colors[i] = 0xFF000000 # Black with full alpha
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
# No special handling needed - parameters are accessed via virtual members
|
||||
# The default fire palette is set up by factory methods when needed
|
||||
end
|
||||
|
||||
# Simple pseudo-random number generator
|
||||
# Uses a linear congruential generator for consistent results
|
||||
def _random()
|
||||
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
return self.random_seed
|
||||
end
|
||||
|
||||
# Get random number in range [0, max)
|
||||
def _random_range(max)
|
||||
if max <= 0
|
||||
return 0
|
||||
end
|
||||
return self._random() % max
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Check if it's time to update the fire simulation
|
||||
# Update frequency is based on flicker_speed (Hz)
|
||||
var flicker_speed = self.flicker_speed # Cache parameter value
|
||||
var update_interval = 1000 / flicker_speed # milliseconds between updates
|
||||
if time_ms - self.last_update >= update_interval
|
||||
self.last_update = time_ms
|
||||
self._update_fire_simulation(time_ms)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update the fire simulation
|
||||
def _update_fire_simulation(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var cooling_rate = self.cooling_rate
|
||||
var sparking_rate = self.sparking_rate
|
||||
var intensity = self.intensity
|
||||
var flicker_amount = self.flicker_amount
|
||||
var color_param = self.color
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure buffers are correct size
|
||||
if size(self.heat_map) != strip_length
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Step 1: Cool down every pixel a little
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
var cooldown = self._random_range(tasmota.scale_uint(cooling_rate, 0, 255, 0, 10) + 2)
|
||||
if cooldown >= self.heat_map[i]
|
||||
self.heat_map[i] = 0
|
||||
else
|
||||
self.heat_map[i] -= cooldown
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Step 2: Heat from each pixel drifts 'up' and diffuses a little
|
||||
# Only do this if we have at least 3 pixels
|
||||
if strip_length >= 3
|
||||
var k = strip_length - 1
|
||||
while k >= 2
|
||||
var heat_avg = (self.heat_map[k-1] + self.heat_map[k-2] + self.heat_map[k-2]) / 3
|
||||
self.heat_map[k] = heat_avg
|
||||
k -= 1
|
||||
end
|
||||
end
|
||||
|
||||
# Step 3: Randomly ignite new 'sparks' of heat near the bottom
|
||||
if self._random_range(255) < sparking_rate
|
||||
var spark_pos = self._random_range(7) # Sparks only in bottom 7 pixels
|
||||
var spark_heat = self._random_range(95) + 160 # Heat between 160-255
|
||||
if spark_pos < strip_length
|
||||
self.heat_map[spark_pos] = spark_heat
|
||||
end
|
||||
end
|
||||
|
||||
# Step 4: Convert heat to colors
|
||||
i = 0
|
||||
while i < strip_length
|
||||
var heat = self.heat_map[i]
|
||||
|
||||
# Apply base intensity scaling
|
||||
heat = tasmota.scale_uint(heat, 0, 255, 0, intensity)
|
||||
|
||||
# Add flicker effect
|
||||
if flicker_amount > 0
|
||||
var flicker = self._random_range(flicker_amount)
|
||||
# Randomly add or subtract flicker
|
||||
if self._random_range(2) == 0
|
||||
heat = heat + flicker
|
||||
else
|
||||
if heat > flicker
|
||||
heat = heat - flicker
|
||||
else
|
||||
heat = 0
|
||||
end
|
||||
end
|
||||
|
||||
# Clamp to valid range
|
||||
if heat > 255
|
||||
heat = 255
|
||||
end
|
||||
end
|
||||
|
||||
# Get color from provider based on heat value
|
||||
var color = 0xFF000000 # Default to black
|
||||
if heat > 0
|
||||
# Get the color parameter (may be nil for default)
|
||||
var resolved_color = color_param
|
||||
|
||||
# If color is nil, create default fire palette
|
||||
if resolved_color == nil
|
||||
# Create default fire palette on demand
|
||||
var fire_provider = animation.rich_palette(self.engine)
|
||||
fire_provider.palette = animation.PALETTE_FIRE
|
||||
fire_provider.cycle_period = 0 # Use value-based color mapping, not time-based
|
||||
fire_provider.transition_type = 1 # Use sine transition (smooth)
|
||||
fire_provider.brightness = 255
|
||||
fire_provider.set_range(0, 255)
|
||||
resolved_color = fire_provider
|
||||
end
|
||||
|
||||
# If the color is a provider that supports get_color_for_value, use it
|
||||
if animation.is_color_provider(resolved_color) && resolved_color.get_color_for_value != nil
|
||||
# Use value-based color mapping for heat
|
||||
color = resolved_color.get_color_for_value(heat, 0)
|
||||
else
|
||||
# Use the resolved color and apply heat as brightness scaling
|
||||
color = resolved_color
|
||||
|
||||
# Apply heat as brightness scaling
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
r = tasmota.scale_uint(heat, 0, 255, 0, r)
|
||||
g = tasmota.scale_uint(heat, 0, 255, 0, g)
|
||||
b = tasmota.scale_uint(heat, 0, 255, 0, b)
|
||||
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render the fire to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Render each pixel with its current color
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Override start method for timing control
|
||||
def start(time_ms)
|
||||
# Call parent start first
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Reset timing and reinitialize buffers
|
||||
self.last_update = 0
|
||||
self._initialize_buffers()
|
||||
|
||||
# Reset random seed
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"FireAnimation(intensity={self.intensity}, flicker_speed={self.flicker_speed}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
return {'fire_animation': FireAnimation}
|
||||
270
lib/libesp32/berry_animation/src/animations/gradient.be
Normal file
270
lib/libesp32/berry_animation/src/animations/gradient.be
Normal file
@ -0,0 +1,270 @@
|
||||
# Gradient animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates smooth color gradients that can be linear or radial,
|
||||
# with optional movement and color transitions over time.
|
||||
|
||||
#@ solidify:GradientAnimation,weak
|
||||
class GradientAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var phase_offset # Current phase offset for movement
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": nil, "nillable": true},
|
||||
"gradient_type": {"min": 0, "max": 1, "default": 0},
|
||||
"direction": {"min": 0, "max": 255, "default": 0},
|
||||
"center_pos": {"min": 0, "max": 255, "default": 128},
|
||||
"spread": {"min": 1, "max": 255, "default": 255},
|
||||
"movement_speed": {"min": 0, "max": 255, "default": 0}
|
||||
}
|
||||
|
||||
# Initialize a new Gradient animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.current_colors = []
|
||||
self.phase_offset = 0
|
||||
|
||||
# Initialize with default strip length from engine
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
# No special handling needed for most parameters
|
||||
# The virtual parameter system handles storage and validation
|
||||
|
||||
# Handle strip length changes from engine
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
if size(self.current_colors) != current_strip_length
|
||||
self.current_colors.resize(current_strip_length)
|
||||
var i = size(self.current_colors)
|
||||
while i < current_strip_length
|
||||
if i >= size(self.current_colors) || self.current_colors[i] == nil
|
||||
if i < size(self.current_colors)
|
||||
self.current_colors[i] = 0xFF000000
|
||||
end
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Cache parameter values for performance
|
||||
var movement_speed = self.movement_speed
|
||||
|
||||
# Update movement phase if movement is enabled
|
||||
if movement_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Movement speed: 0-255 maps to 0-10 cycles per second
|
||||
var cycles_per_second = tasmota.scale_uint(movement_speed, 0, 255, 0, 10)
|
||||
if cycles_per_second > 0
|
||||
self.phase_offset = (elapsed * cycles_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate gradient colors
|
||||
self._calculate_gradient(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate gradient colors for all pixels
|
||||
def _calculate_gradient(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var gradient_type = self.gradient_type
|
||||
var color_param = self.color
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure current_colors array matches strip length
|
||||
if size(self.current_colors) != strip_length
|
||||
self.current_colors.resize(strip_length)
|
||||
end
|
||||
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
var gradient_pos = 0
|
||||
|
||||
if gradient_type == 0
|
||||
# Linear gradient
|
||||
gradient_pos = self._calculate_linear_position(i, strip_length)
|
||||
else
|
||||
# Radial gradient
|
||||
gradient_pos = self._calculate_radial_position(i, strip_length)
|
||||
end
|
||||
|
||||
# Apply movement offset
|
||||
gradient_pos = (gradient_pos + self.phase_offset) % 256
|
||||
|
||||
# Get color from provider
|
||||
var color = 0xFF000000
|
||||
|
||||
# Handle default rainbow gradient if color is nil
|
||||
if color_param == nil
|
||||
# Create default rainbow gradient on-the-fly
|
||||
var hue = tasmota.scale_uint(gradient_pos, 0, 255, 0, 359)
|
||||
import light_state
|
||||
var ls = light_state(3) # Create RGB light state
|
||||
ls.HsToRgb(hue, 255) # Convert HSV to RGB
|
||||
color = 0xFF000000 | (ls.r << 16) | (ls.g << 8) | ls.b
|
||||
elif animation.is_color_provider(color_param) && color_param.get_color_for_value != nil
|
||||
color = color_param.get_color_for_value(gradient_pos, 0)
|
||||
elif animation.is_value_provider(color_param)
|
||||
# Use resolve_value with position influence
|
||||
color = self.resolve_value(color_param, "color", time_ms + gradient_pos * 10)
|
||||
elif type(color_param) == "int"
|
||||
# Single color - create gradient from black to color
|
||||
var intensity = gradient_pos
|
||||
var r = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 16) & 0xFF)
|
||||
var g = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 8) & 0xFF)
|
||||
var b = tasmota.scale_uint(intensity, 0, 255, 0, color_param & 0xFF)
|
||||
color = 0xFF000000 | (r << 16) | (g << 8) | b
|
||||
else
|
||||
color = color_param
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate position for linear gradient
|
||||
def _calculate_linear_position(pixel, strip_length)
|
||||
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Cache parameter values
|
||||
var direction = self.direction
|
||||
var spread = self.spread
|
||||
|
||||
# Apply direction (0=left-to-right, 128=center-out, 255=right-to-left)
|
||||
if direction <= 128
|
||||
# Forward direction with varying start point
|
||||
var start_offset = tasmota.scale_uint(direction, 0, 128, 0, 128)
|
||||
strip_pos = (strip_pos + start_offset) % 256
|
||||
else
|
||||
# Reverse direction
|
||||
var reverse_amount = tasmota.scale_uint(direction, 128, 255, 0, 255)
|
||||
strip_pos = 255 - ((strip_pos + reverse_amount) % 256)
|
||||
end
|
||||
|
||||
# Apply spread (compress or expand the gradient)
|
||||
strip_pos = tasmota.scale_uint(strip_pos, 0, 255, 0, spread)
|
||||
|
||||
return strip_pos
|
||||
end
|
||||
|
||||
# Calculate position for radial gradient
|
||||
def _calculate_radial_position(pixel, strip_length)
|
||||
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Cache parameter values
|
||||
var center = self.center_pos
|
||||
var spread = self.spread
|
||||
|
||||
# Calculate distance from center
|
||||
var distance = 0
|
||||
if strip_pos >= center
|
||||
distance = strip_pos - center
|
||||
else
|
||||
distance = center - strip_pos
|
||||
end
|
||||
|
||||
# Scale distance by spread
|
||||
distance = tasmota.scale_uint(distance, 0, 128, 0, spread)
|
||||
if distance > 255
|
||||
distance = 255
|
||||
end
|
||||
|
||||
return distance
|
||||
end
|
||||
|
||||
# Render gradient to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length && i < frame.width
|
||||
if i < size(self.current_colors)
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var gradient_type = self.gradient_type
|
||||
var color = self.color
|
||||
var movement_speed = self.movement_speed
|
||||
var priority = self.priority
|
||||
|
||||
var type_str = gradient_type == 0 ? "linear" : "radial"
|
||||
var color_str
|
||||
if animation.is_value_provider(color)
|
||||
color_str = str(color)
|
||||
elif color == nil
|
||||
color_str = "rainbow"
|
||||
else
|
||||
color_str = f"0x{color :08x}"
|
||||
end
|
||||
return f"GradientAnimation({type_str}, color={color_str}, movement={movement_speed}, priority={priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following parameterized class specification
|
||||
|
||||
# Create a rainbow linear gradient
|
||||
def gradient_rainbow_linear(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = nil # Default rainbow
|
||||
anim.gradient_type = 0 # Linear
|
||||
anim.direction = 0 # Left-to-right
|
||||
anim.movement_speed = 50 # Medium movement
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a rainbow radial gradient
|
||||
def gradient_rainbow_radial(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = nil # Default rainbow
|
||||
anim.gradient_type = 1 # Radial
|
||||
anim.center_pos = 128 # Center
|
||||
anim.movement_speed = 30 # Slow movement
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a two-color linear gradient
|
||||
def gradient_two_color_linear(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = 0xFFFF0000 # Default red gradient
|
||||
anim.gradient_type = 0 # Linear
|
||||
anim.direction = 0 # Left-to-right
|
||||
anim.movement_speed = 0 # Static
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'gradient_animation': GradientAnimation, 'gradient_rainbow_linear': gradient_rainbow_linear, 'gradient_rainbow_radial': gradient_rainbow_radial, 'gradient_two_color_linear': gradient_two_color_linear}
|
||||
302
lib/libesp32/berry_animation/src/animations/jitter.be
Normal file
302
lib/libesp32/berry_animation/src/animations/jitter.be
Normal file
@ -0,0 +1,302 @@
|
||||
# Jitter animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation adds random jitter/shake effects to patterns with configurable
|
||||
# intensity, frequency, and jitter types (position, color, brightness).
|
||||
|
||||
#@ solidify:JitterAnimation,weak
|
||||
class JitterAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var random_seed # Seed for random number generation
|
||||
var last_jitter_time # Last time jitter was updated
|
||||
var jitter_offsets # Array of current jitter offsets per pixel
|
||||
var source_frame # Frame buffer for source animation
|
||||
var current_colors # Array of current colors for each pixel
|
||||
|
||||
# Parameter definitions
|
||||
static var PARAMS = {
|
||||
"source_animation": {"type": "instance", "default": nil},
|
||||
"jitter_intensity": {"min": 0, "max": 255, "default": 100},
|
||||
"jitter_frequency": {"min": 0, "max": 255, "default": 60},
|
||||
"jitter_type": {"min": 0, "max": 3, "default": 0},
|
||||
"position_range": {"min": 0, "max": 255, "default": 50},
|
||||
"color_range": {"min": 0, "max": 255, "default": 30},
|
||||
"brightness_range": {"min": 0, "max": 255, "default": 40}
|
||||
}
|
||||
|
||||
# Initialize a new Jitter animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize random seed using engine time
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
# Initialize state
|
||||
self.last_jitter_time = 0
|
||||
|
||||
# Initialize buffers
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Initialize buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
self.jitter_offsets = []
|
||||
self.jitter_offsets.resize(current_strip_length)
|
||||
self.source_frame = animation.frame_buffer(current_strip_length)
|
||||
self.current_colors = []
|
||||
self.current_colors.resize(current_strip_length)
|
||||
|
||||
# Initialize arrays
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
self.jitter_offsets[i] = 0
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Override start method for lifecycle control
|
||||
def start(time_ms)
|
||||
# Call parent start first (handles ValueProvider propagation)
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Reset jitter timing
|
||||
self.last_jitter_time = time_ms != nil ? time_ms : self.engine.time_ms
|
||||
|
||||
# Reinitialize buffers in case strip length changed
|
||||
self._initialize_buffers()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Simple pseudo-random number generator
|
||||
def _random()
|
||||
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
return self.random_seed
|
||||
end
|
||||
|
||||
# Get random number in range [-max_range, max_range]
|
||||
def _random_range(max_range)
|
||||
if max_range <= 0
|
||||
return 0
|
||||
end
|
||||
var val = self._random() % (max_range * 2 + 1)
|
||||
return val - max_range
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var jitter_frequency = self.jitter_frequency
|
||||
var source_animation = self.source_animation
|
||||
|
||||
# Update jitter at specified frequency
|
||||
if jitter_frequency > 0
|
||||
# Frequency: 0-255 maps to 0-30 Hz
|
||||
var hz = tasmota.scale_uint(jitter_frequency, 0, 255, 0, 30)
|
||||
var interval = hz > 0 ? 1000 / hz : 1000
|
||||
|
||||
if time_ms - self.last_jitter_time >= interval
|
||||
self.last_jitter_time = time_ms
|
||||
self._update_jitter()
|
||||
end
|
||||
end
|
||||
|
||||
# Update source animation if it exists
|
||||
if source_animation != nil
|
||||
source_animation.update(time_ms)
|
||||
end
|
||||
|
||||
# Calculate jittered colors
|
||||
self._calculate_jitter()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update jitter offsets
|
||||
def _update_jitter()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var jitter_intensity = self.jitter_intensity
|
||||
var max_offset = tasmota.scale_uint(jitter_intensity, 0, 255, 0, 10)
|
||||
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
# Generate new random offset based on intensity
|
||||
self.jitter_offsets[i] = self._random_range(max_offset)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate jittered colors for all pixels
|
||||
def _calculate_jitter()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var source_animation = self.source_animation
|
||||
var jitter_type = self.jitter_type
|
||||
var position_range = self.position_range
|
||||
|
||||
# Clear source frame
|
||||
self.source_frame.clear()
|
||||
|
||||
# Render source animation to frame
|
||||
if source_animation != nil
|
||||
source_animation.render(self.source_frame, 0)
|
||||
end
|
||||
|
||||
# Apply jitter transformation
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
var base_color = 0xFF000000
|
||||
|
||||
if jitter_type == 0 || jitter_type == 3
|
||||
# Position jitter
|
||||
var jitter_pixels = tasmota.scale_uint(self.jitter_offsets[i], -10, 10, -position_range / 10, position_range / 10)
|
||||
var source_pos = i + jitter_pixels
|
||||
|
||||
# Clamp to strip bounds
|
||||
if source_pos >= 0 && source_pos < current_strip_length
|
||||
base_color = self.source_frame.get_pixel_color(source_pos)
|
||||
else
|
||||
base_color = 0xFF000000
|
||||
end
|
||||
else
|
||||
# No position jitter, use original position
|
||||
base_color = self.source_frame.get_pixel_color(i)
|
||||
end
|
||||
|
||||
# Apply color and brightness jitter
|
||||
if (jitter_type == 1 || jitter_type == 2 || jitter_type == 3) && base_color != 0xFF000000
|
||||
base_color = self._apply_color_jitter(base_color, i)
|
||||
end
|
||||
|
||||
self.current_colors[i] = base_color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Apply color/brightness jitter to a color
|
||||
def _apply_color_jitter(color, pixel_index)
|
||||
# Cache parameter values for performance
|
||||
var jitter_type = self.jitter_type
|
||||
var color_range = self.color_range
|
||||
var brightness_range = self.brightness_range
|
||||
|
||||
# Extract ARGB components
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
if jitter_type == 1 || jitter_type == 3
|
||||
# Color jitter - add random values to RGB
|
||||
var color_jitter = tasmota.scale_uint(color_range, 0, 255, 0, 30)
|
||||
r += self._random_range(color_jitter)
|
||||
g += self._random_range(color_jitter)
|
||||
b += self._random_range(color_jitter)
|
||||
end
|
||||
|
||||
if jitter_type == 2 || jitter_type == 3
|
||||
# Brightness jitter - scale all RGB components
|
||||
var brightness_jitter = tasmota.scale_uint(brightness_range, 0, 255, 0, 50)
|
||||
var brightness_factor = 128 + self._random_range(brightness_jitter)
|
||||
if brightness_factor < 0
|
||||
brightness_factor = 0
|
||||
elif brightness_factor > 255
|
||||
brightness_factor = 255
|
||||
end
|
||||
|
||||
r = tasmota.scale_uint(r, 0, 255, 0, brightness_factor)
|
||||
g = tasmota.scale_uint(g, 0, 255, 0, brightness_factor)
|
||||
b = tasmota.scale_uint(b, 0, 255, 0, brightness_factor)
|
||||
end
|
||||
|
||||
# Clamp components to valid range
|
||||
if r > 255
|
||||
r = 255
|
||||
elif r < 0
|
||||
r = 0
|
||||
end
|
||||
if g > 255
|
||||
g = 255
|
||||
elif g < 0
|
||||
g = 0
|
||||
end
|
||||
if b > 255
|
||||
b = 255
|
||||
elif b < 0
|
||||
b = 0
|
||||
end
|
||||
|
||||
return (a << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
|
||||
# Render jitter to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var type_names = ["position", "color", "brightness", "all"]
|
||||
var jitter_type = self.jitter_type
|
||||
var type_name = type_names[jitter_type] != nil ? type_names[jitter_type] : "unknown"
|
||||
return f"JitterAnimation({type_name}, intensity={self.jitter_intensity}, frequency={self.jitter_frequency})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions for common jitter presets
|
||||
|
||||
# Create a position jitter animation
|
||||
def jitter_position(engine)
|
||||
var anim = animation.jitter_animation(engine)
|
||||
anim.jitter_type = 0
|
||||
anim.position_range = 50
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a color jitter animation
|
||||
def jitter_color(engine)
|
||||
var anim = animation.jitter_animation(engine)
|
||||
anim.jitter_type = 1
|
||||
anim.color_range = 30
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a brightness jitter animation
|
||||
def jitter_brightness(engine)
|
||||
var anim = animation.jitter_animation(engine)
|
||||
anim.jitter_type = 2
|
||||
anim.brightness_range = 40
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a full jitter animation (all types)
|
||||
def jitter_all(engine)
|
||||
var anim = animation.jitter_animation(engine)
|
||||
anim.jitter_type = 3
|
||||
anim.position_range = 50
|
||||
anim.color_range = 30
|
||||
anim.brightness_range = 40
|
||||
return anim
|
||||
end
|
||||
|
||||
return {
|
||||
'jitter_animation': JitterAnimation,
|
||||
'jitter_position': jitter_position,
|
||||
'jitter_color': jitter_color,
|
||||
'jitter_brightness': jitter_brightness,
|
||||
'jitter_all': jitter_all
|
||||
}
|
||||
316
lib/libesp32/berry_animation/src/animations/noise.be
Normal file
316
lib/libesp32/berry_animation/src/animations/noise.be
Normal file
@ -0,0 +1,316 @@
|
||||
# Noise animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates pseudo-random noise patterns with configurable
|
||||
# scale, speed, and color mapping through palettes or single colors.
|
||||
|
||||
#@ solidify:NoiseAnimation,weak
|
||||
class NoiseAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var time_offset # Current time offset for animation
|
||||
var noise_table # Pre-computed noise values for performance
|
||||
|
||||
# Parameter definitions following new specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": nil},
|
||||
"scale": {"min": 1, "max": 255, "default": 50},
|
||||
"speed": {"min": 0, "max": 255, "default": 30},
|
||||
"octaves": {"min": 1, "max": 4, "default": 1},
|
||||
"persistence": {"min": 0, "max": 255, "default": 128},
|
||||
"seed": {"min": 0, "max": 65535, "default": 12345}
|
||||
}
|
||||
|
||||
# Initialize a new Noise animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.current_colors = []
|
||||
self.current_colors.resize(strip_length)
|
||||
self.time_offset = 0
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Initialize noise table - will be done in start method
|
||||
self.noise_table = []
|
||||
|
||||
# Set default color if not set
|
||||
if self.color == nil
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
self.color = rainbow_provider
|
||||
end
|
||||
end
|
||||
|
||||
# Override start method for initialization
|
||||
def start(time_ms)
|
||||
# Call parent start first
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Initialize noise table with current seed
|
||||
self._init_noise_table()
|
||||
|
||||
# Reset time offset
|
||||
self.time_offset = 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Initialize noise lookup table for performance
|
||||
def _init_noise_table()
|
||||
self.noise_table = []
|
||||
self.noise_table.resize(256)
|
||||
|
||||
# Generate pseudo-random values using seed
|
||||
var current_seed = self.seed
|
||||
var rng_state = current_seed
|
||||
var i = 0
|
||||
while i < 256
|
||||
rng_state = (rng_state * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
self.noise_table[i] = rng_state % 256
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Override setmember to handle color conversion
|
||||
def setmember(name, value)
|
||||
if name == "color" && type(value) == "int"
|
||||
# Convert integer color to gradient palette from black to color
|
||||
var palette = bytes()
|
||||
palette.add(0x00, 1) # Position 0: black
|
||||
palette.add(0x00, 1) # R
|
||||
palette.add(0x00, 1) # G
|
||||
palette.add(0x00, 1) # B
|
||||
palette.add(0xFF, 1) # Position 255: full color
|
||||
palette.add((value >> 16) & 0xFF, 1) # R
|
||||
palette.add((value >> 8) & 0xFF, 1) # G
|
||||
palette.add(value & 0xFF, 1) # B
|
||||
|
||||
var gradient_provider = animation.rich_palette(self.engine)
|
||||
gradient_provider.palette = palette
|
||||
gradient_provider.cycle_period = 5000
|
||||
gradient_provider.transition_type = 1
|
||||
gradient_provider.brightness = 255
|
||||
gradient_provider.range_min = 0
|
||||
gradient_provider.range_max = 255
|
||||
|
||||
# Set the gradient provider instead of the integer
|
||||
super(self).setmember(name, gradient_provider)
|
||||
else
|
||||
# Use parent implementation for other parameters
|
||||
super(self).setmember(name, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "seed"
|
||||
self._init_noise_table()
|
||||
end
|
||||
|
||||
# Update current_colors array size when strip length changes via engine
|
||||
var new_strip_length = self.engine.get_strip_length()
|
||||
if size(self.current_colors) != new_strip_length
|
||||
self.current_colors.resize(new_strip_length)
|
||||
var i = size(self.current_colors)
|
||||
while i < new_strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Simple noise function using lookup table
|
||||
def _noise_1d(x)
|
||||
var ix = int(x) & 255
|
||||
var fx = x - int(x)
|
||||
|
||||
# Get noise values at integer positions
|
||||
var a = self.noise_table[ix]
|
||||
var b = self.noise_table[(ix + 1) & 255]
|
||||
|
||||
# Linear interpolation using integer math
|
||||
var lerp_amount = tasmota.scale_uint(int(fx * 256), 0, 256, 0, 255)
|
||||
return tasmota.scale_uint(lerp_amount, 0, 255, a, b)
|
||||
end
|
||||
|
||||
# Fractal noise with multiple octaves
|
||||
def _fractal_noise(x, time_offset)
|
||||
var value = 0
|
||||
var amplitude = 255
|
||||
var current_scale = self.scale
|
||||
var current_octaves = self.octaves
|
||||
var current_persistence = self.persistence
|
||||
var frequency = current_scale
|
||||
var max_value = 0
|
||||
|
||||
var octave = 0
|
||||
while octave < current_octaves
|
||||
var sample_x = tasmota.scale_uint(x * frequency, 0, 255 * 255, 0, 255) + time_offset
|
||||
var noise_val = self._noise_1d(sample_x)
|
||||
|
||||
value += tasmota.scale_uint(noise_val, 0, 255, 0, amplitude)
|
||||
max_value += amplitude
|
||||
|
||||
amplitude = tasmota.scale_uint(amplitude, 0, 255, 0, current_persistence)
|
||||
frequency = frequency * 2
|
||||
if frequency > 255
|
||||
frequency = 255
|
||||
end
|
||||
|
||||
octave += 1
|
||||
end
|
||||
|
||||
# Normalize to 0-255 range
|
||||
if max_value > 0
|
||||
value = tasmota.scale_uint(value, 0, max_value, 0, 255)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Update time offset based on speed
|
||||
var current_speed = self.speed
|
||||
if current_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Speed: 0-255 maps to 0-5 units per second
|
||||
var units_per_second = tasmota.scale_uint(current_speed, 0, 255, 0, 5)
|
||||
if units_per_second > 0
|
||||
self.time_offset = (elapsed * units_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate noise colors
|
||||
self._calculate_noise(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate noise colors for all pixels
|
||||
def _calculate_noise(time_ms)
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var current_color = self.color
|
||||
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate noise value for this pixel
|
||||
var noise_value = self._fractal_noise(i, self.time_offset)
|
||||
|
||||
# Get color from provider
|
||||
var color = 0xFF000000
|
||||
|
||||
# If the color is a provider that supports get_color_for_value, use it
|
||||
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
|
||||
color = current_color.get_color_for_value(noise_value, 0)
|
||||
else
|
||||
# Use resolve_value with noise influence
|
||||
color = self.resolve_value(current_color, "color", time_ms + noise_value * 10)
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render noise to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var current_color = self.color
|
||||
var color_str
|
||||
if animation.is_value_provider(current_color)
|
||||
color_str = str(current_color)
|
||||
else
|
||||
color_str = f"0x{current_color :08x}"
|
||||
end
|
||||
return f"NoiseAnimation(color={color_str}, scale={self.scale}, speed={self.speed}, octaves={self.octaves}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following new specification
|
||||
|
||||
# Create a rainbow noise animation preset
|
||||
def noise_rainbow(engine)
|
||||
var anim = animation.noise_animation(engine)
|
||||
# Set up rainbow color provider
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
anim.color = rainbow_provider
|
||||
anim.scale = 50
|
||||
anim.speed = 30
|
||||
anim.octaves = 1
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a single color noise animation preset
|
||||
def noise_single_color(engine)
|
||||
var anim = animation.noise_animation(engine)
|
||||
# Set up a simple white color - user can change it after creation
|
||||
anim.color = 0xFFFFFFFF
|
||||
anim.scale = 50
|
||||
anim.speed = 30
|
||||
anim.octaves = 1
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a fractal noise animation preset
|
||||
def noise_fractal(engine)
|
||||
var anim = animation.noise_animation(engine)
|
||||
# Set up rainbow color provider
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
anim.color = rainbow_provider
|
||||
anim.scale = 30
|
||||
anim.speed = 20
|
||||
anim.octaves = 3
|
||||
anim.persistence = 128
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'noise_animation': NoiseAnimation, 'noise_rainbow': noise_rainbow, 'noise_single_color': noise_single_color, 'noise_fractal': noise_fractal}
|
||||
308
lib/libesp32/berry_animation/src/animations/palette_pattern.be
Normal file
308
lib/libesp32/berry_animation/src/animations/palette_pattern.be
Normal file
@ -0,0 +1,308 @@
|
||||
# PalettePattern animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation applies colors from a color provider to specific patterns or regions.
|
||||
# It allows for more complex visual effects by combining palette colors with patterns.
|
||||
#
|
||||
# This version supports both RichPaletteAnimation and ColorProvider instances as color sources,
|
||||
# allowing for more flexible usage of color providers.
|
||||
|
||||
#@ solidify:PalettePatternAnimation,weak
|
||||
class PalettePatternAnimation : animation.animation
|
||||
var value_buffer # Buffer to store values for each pixel
|
||||
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
# Palette pattern-specific parameters
|
||||
"color_source": {"default": nil, "type": "instance"},
|
||||
"pattern_func": {"default": nil, "type": "function"}
|
||||
}
|
||||
|
||||
# Initialize a new PalettePattern animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.value_buffer = []
|
||||
|
||||
# Initialize value buffer with default frame width
|
||||
self._initialize_value_buffer()
|
||||
end
|
||||
|
||||
# Initialize the value buffer based on current strip length
|
||||
def _initialize_value_buffer()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.value_buffer.resize(strip_length)
|
||||
|
||||
# Initialize with zeros
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.value_buffer[i] = 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Update the value buffer based on the current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
def _update_value_buffer(time_ms)
|
||||
var pattern_func = self.pattern_func
|
||||
if pattern_func == nil
|
||||
return
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize buffer if strip length changed
|
||||
if size(self.value_buffer) != strip_length
|
||||
self.value_buffer.resize(strip_length)
|
||||
end
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.value_buffer[i] = pattern_func(i, time_ms, self)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
# Update the value buffer
|
||||
self._update_value_buffer(elapsed)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the pattern to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use provided time or default to engine time
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
# Get current parameter values (cached for performance)
|
||||
var color_source = self.color_source
|
||||
if color_source == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
# Apply colors from the color source to each pixel based on its value
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length && i < frame.width
|
||||
var value = self.value_buffer[i]
|
||||
var color
|
||||
|
||||
# Check if color_source is a ColorProvider or an animation with get_color_for_value method
|
||||
if color_source.get_color_for_value != nil
|
||||
# It's a ColorProvider or compatible object
|
||||
color = color_source.get_color_for_value(value, elapsed)
|
||||
else
|
||||
# Fallback to direct color access (for backward compatibility)
|
||||
color = color_source.current_color
|
||||
end
|
||||
|
||||
frame.set_pixel_color(i, color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "pattern_func" || name == "color_source"
|
||||
# Reinitialize value buffer when pattern or color source changes
|
||||
self._initialize_value_buffer()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
return f"PalettePatternAnimation(strip_length={strip_length}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Wave pattern animation - creates sine wave patterns
|
||||
#@ solidify:PaletteWaveAnimation,weak
|
||||
class PaletteWaveAnimation : PalettePatternAnimation
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
# Wave-specific parameters only
|
||||
"wave_period": {"min": 1, "default": 5000},
|
||||
"wave_length": {"min": 1, "default": 10}
|
||||
}
|
||||
|
||||
# Initialize a new wave pattern animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor
|
||||
super(self).init(engine)
|
||||
|
||||
# Set default name
|
||||
self.name = "palette_wave"
|
||||
end
|
||||
|
||||
# Override _update_value_buffer to generate wave pattern directly
|
||||
def _update_value_buffer(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var wave_period = self.wave_period
|
||||
var wave_length = self.wave_length
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize buffer if strip length changed
|
||||
if size(self.value_buffer) != strip_length
|
||||
self.value_buffer.resize(strip_length)
|
||||
end
|
||||
|
||||
# Calculate the wave position using scale_uint for better precision
|
||||
var position = tasmota.scale_uint(time_ms % wave_period, 0, wave_period, 0, 1000) / 1000.0
|
||||
var offset = int(position * wave_length)
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate the wave value (0-100) using scale_uint
|
||||
var pos_in_wave = (i + offset) % wave_length
|
||||
var angle = tasmota.scale_uint(pos_in_wave, 0, wave_length, 0, 32767) # 0 to 2π in fixed-point
|
||||
var sine_value = tasmota.sine_int(angle) # -4096 to 4096
|
||||
|
||||
# Map sine value from -4096..4096 to 0..100
|
||||
self.value_buffer[i] = tasmota.scale_int(sine_value, -4096, 4096, 0, 100)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Gradient pattern animation - creates shifting gradient patterns
|
||||
#@ solidify:PaletteGradientAnimation,weak
|
||||
class PaletteGradientAnimation : PalettePatternAnimation
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
# Gradient-specific parameters only
|
||||
"shift_period": {"min": 1, "default": 10000}
|
||||
}
|
||||
|
||||
# Initialize a new gradient pattern animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor
|
||||
super(self).init(engine)
|
||||
|
||||
# Set default name
|
||||
self.name = "palette_gradient"
|
||||
end
|
||||
|
||||
# Override _update_value_buffer to generate gradient pattern directly
|
||||
def _update_value_buffer(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var shift_period = self.shift_period
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize buffer if strip length changed
|
||||
if size(self.value_buffer) != strip_length
|
||||
self.value_buffer.resize(strip_length)
|
||||
end
|
||||
|
||||
# Calculate the shift position using scale_uint for better precision
|
||||
var position = tasmota.scale_uint(time_ms % shift_period, 0, shift_period, 0, 1000) / 1000.0
|
||||
var offset = int(position * strip_length)
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate the gradient value (0-100) using scale_uint
|
||||
var pos_in_frame = (i + offset) % strip_length
|
||||
self.value_buffer[i] = tasmota.scale_uint(pos_in_frame, 0, strip_length - 1, 0, 100)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Value meter pattern animation - creates meter/bar patterns based on a value function
|
||||
#@ solidify:PaletteMeterAnimation,weak
|
||||
class PaletteMeterAnimation : PalettePatternAnimation
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
# Meter-specific parameters only
|
||||
"value_func": {"default": nil, "type": "function"}
|
||||
}
|
||||
|
||||
# Initialize a new meter pattern animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor
|
||||
super(self).init(engine)
|
||||
|
||||
# Set default name
|
||||
self.name = "palette_meter"
|
||||
end
|
||||
|
||||
# Override _update_value_buffer to generate meter pattern directly
|
||||
def _update_value_buffer(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var value_func = self.value_func
|
||||
if value_func == nil
|
||||
return
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize buffer if strip length changed
|
||||
if size(self.value_buffer) != strip_length
|
||||
self.value_buffer.resize(strip_length)
|
||||
end
|
||||
|
||||
# Get the current value
|
||||
var current_value = value_func(time_ms, self)
|
||||
|
||||
# Calculate the meter position using scale_uint for better precision
|
||||
var meter_position = tasmota.scale_uint(current_value, 0, 100, 0, strip_length)
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Return 100 if pixel is within the meter, 0 otherwise
|
||||
self.value_buffer[i] = i < meter_position ? 100 : 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
'palette_pattern_animation': PalettePatternAnimation,
|
||||
'palette_wave_animation': PaletteWaveAnimation,
|
||||
'palette_gradient_animation': PaletteGradientAnimation,
|
||||
'palette_meter_animation': PaletteMeterAnimation
|
||||
}
|
||||
249
lib/libesp32/berry_animation/src/animations/plasma.be
Normal file
249
lib/libesp32/berry_animation/src/animations/plasma.be
Normal file
@ -0,0 +1,249 @@
|
||||
# Plasma animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates classic plasma effects using sine wave interference
|
||||
# patterns with configurable frequencies, phases, and time-based animation.
|
||||
|
||||
#@ solidify:PlasmaAnimation,weak
|
||||
class PlasmaAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var time_phase # Current time-based phase
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": nil},
|
||||
"freq_x": {"min": 1, "max": 255, "default": 32},
|
||||
"freq_y": {"min": 1, "max": 255, "default": 23},
|
||||
"phase_x": {"min": 0, "max": 255, "default": 0},
|
||||
"phase_y": {"min": 0, "max": 255, "default": 64},
|
||||
"time_speed": {"min": 0, "max": 255, "default": 50},
|
||||
"blend_mode": {"min": 0, "max": 2, "default": 0}
|
||||
}
|
||||
|
||||
# Initialize a new Plasma animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.time_phase = 0
|
||||
|
||||
# Initialize current_colors array - will be resized when strip length is known
|
||||
self.current_colors = []
|
||||
self._initialize_colors()
|
||||
end
|
||||
|
||||
# Fast sine calculation using Tasmota's optimized sine function
|
||||
# Input: angle in 0-255 range (mapped to 0-2π)
|
||||
# Output: sine value in 0-255 range (mapped from -1 to 1)
|
||||
def _sine(angle)
|
||||
# Map angle from 0-255 to 0-32767 (tasmota.sine_int input range)
|
||||
var tasmota_angle = tasmota.scale_uint(angle, 0, 255, 0, 32767)
|
||||
|
||||
# Get sine value from -4096 to 4096 (representing -1.0 to 1.0)
|
||||
var sine_val = tasmota.sine_int(tasmota_angle)
|
||||
|
||||
# Map from -4096..4096 to 0..255 for plasma calculations
|
||||
return tasmota.scale_uint(sine_val, -4096, 4096, 0, 255)
|
||||
end
|
||||
|
||||
# Initialize colors array based on current strip length
|
||||
def _initialize_colors()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
self.current_colors.resize(strip_length)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Start/restart the animation
|
||||
def start(time_ms)
|
||||
# Call parent start first
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Initialize default color if not set
|
||||
if self.color == nil
|
||||
var rainbow_provider = animation.rich_palette(self.engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
self.color = rainbow_provider
|
||||
end
|
||||
|
||||
# Reset time phase
|
||||
self.time_phase = 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "color" && value == nil
|
||||
# Reset to default rainbow palette when color is set to nil
|
||||
var rainbow_provider = animation.rich_palette(self.engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
# Set the parameter directly to avoid recursion
|
||||
self.set_param("color", rainbow_provider)
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Update time phase based on speed
|
||||
var current_time_speed = self.time_speed
|
||||
if current_time_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Speed: 0-255 maps to 0-8 cycles per second
|
||||
var cycles_per_second = tasmota.scale_uint(current_time_speed, 0, 255, 0, 8)
|
||||
if cycles_per_second > 0
|
||||
self.time_phase = (elapsed * cycles_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate plasma colors
|
||||
self._calculate_plasma(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate plasma colors for all pixels
|
||||
def _calculate_plasma(time_ms)
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure colors array is properly sized
|
||||
if size(self.current_colors) != strip_length
|
||||
self._initialize_colors()
|
||||
end
|
||||
|
||||
# Cache parameter values for performance
|
||||
var current_freq_x = self.freq_x
|
||||
var current_freq_y = self.freq_y
|
||||
var current_phase_x = self.phase_x
|
||||
var current_phase_y = self.phase_y
|
||||
var current_blend_mode = self.blend_mode
|
||||
var current_color = self.color
|
||||
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Map pixel position to 0-255 range
|
||||
var x = tasmota.scale_uint(i, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Calculate plasma components
|
||||
var comp1 = self._sine((x * current_freq_x / 32) + current_phase_x + self.time_phase)
|
||||
var comp2 = self._sine((x * current_freq_y / 32) + current_phase_y + (self.time_phase * 2))
|
||||
|
||||
# Blend components based on blend mode
|
||||
var plasma_value = 0
|
||||
if current_blend_mode == 0
|
||||
# Add mode
|
||||
plasma_value = (comp1 + comp2) / 2
|
||||
elif current_blend_mode == 1
|
||||
# Multiply mode
|
||||
plasma_value = tasmota.scale_uint(comp1, 0, 255, 0, comp2)
|
||||
else
|
||||
# Average mode (default)
|
||||
plasma_value = (comp1 + comp2) / 2
|
||||
end
|
||||
|
||||
# Ensure value is in valid range
|
||||
if plasma_value > 255
|
||||
plasma_value = 255
|
||||
elif plasma_value < 0
|
||||
plasma_value = 0
|
||||
end
|
||||
|
||||
# Get color from provider
|
||||
var color = 0xFF000000
|
||||
|
||||
# If the color is a provider that supports get_color_for_value, use it
|
||||
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
|
||||
color = current_color.get_color_for_value(plasma_value, 0)
|
||||
else
|
||||
# Use resolve_value with plasma influence
|
||||
color = self.resolve_value(current_color, "color", time_ms + plasma_value * 10)
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render plasma to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var color_str
|
||||
var current_color = self.color
|
||||
if animation.is_value_provider(current_color)
|
||||
color_str = str(current_color)
|
||||
else
|
||||
color_str = f"0x{current_color :08x}"
|
||||
end
|
||||
return f"PlasmaAnimation(color={color_str}, freq_x={self.freq_x}, freq_y={self.freq_y}, time_speed={self.time_speed}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions
|
||||
|
||||
# Create a classic rainbow plasma animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
# @return PlasmaAnimation - A new plasma animation instance with rainbow colors
|
||||
def plasma_rainbow(engine)
|
||||
var anim = animation.plasma_animation(engine)
|
||||
# Use default rainbow color (nil triggers rainbow in on_param_changed)
|
||||
anim.color = nil
|
||||
anim.time_speed = 50
|
||||
anim.name = "plasma_rainbow"
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a fast plasma animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
# @return PlasmaAnimation - A new fast plasma animation instance
|
||||
def plasma_fast(engine)
|
||||
var anim = animation.plasma_animation(engine)
|
||||
anim.color = nil # Default rainbow
|
||||
anim.time_speed = 150
|
||||
anim.freq_x = 48
|
||||
anim.freq_y = 35
|
||||
anim.name = "plasma_fast"
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'plasma_animation': PlasmaAnimation, 'plasma_rainbow': plasma_rainbow, 'plasma_fast': plasma_fast}
|
||||
@ -1,208 +0,0 @@
|
||||
# Pulse Animation for Berry Animation Framework
|
||||
#
|
||||
# A pulse animation takes any pattern and makes it pulse between min and max opacity.
|
||||
# This demonstrates how animations can be composed with patterns.
|
||||
|
||||
#@ solidify:PulseAnimation,weak
|
||||
class PulseAnimation : animation.animation
|
||||
var base_pattern # The pattern to pulse (can be any Pattern)
|
||||
var min_opacity # Minimum opacity level (0-255)
|
||||
var max_opacity # Maximum opacity level (0-255)
|
||||
var pulse_period # Time for one complete pulse cycle in milliseconds
|
||||
var current_pulse_opacity # Current pulse opacity (calculated during update)
|
||||
|
||||
# Initialize a new Pulse animation
|
||||
#
|
||||
# @param base_pattern: Pattern - The pattern to pulse
|
||||
# @param min_opacity: int - Minimum opacity level (0-255)
|
||||
# @param max_opacity: int - Maximum opacity level (0-255)
|
||||
# @param pulse_period: int - Time for one complete pulse cycle in milliseconds
|
||||
# @param priority: int - Rendering priority (higher = on top)
|
||||
# @param duration: int - Duration in milliseconds, 0 for infinite
|
||||
# @param loop: bool - Whether animation should loop when duration is reached
|
||||
# @param opacity: int - Base animation opacity (0-255), defaults to 255
|
||||
# @param name: string - Optional name for the animation
|
||||
def init(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
|
||||
# Call parent Animation constructor
|
||||
super(self).init(priority, duration, loop, opacity, name != nil ? name : "pulse")
|
||||
|
||||
# Set pulse-specific properties with defaults
|
||||
self.base_pattern = base_pattern
|
||||
self.min_opacity = min_opacity != nil ? min_opacity : 0
|
||||
self.max_opacity = max_opacity != nil ? max_opacity : 255
|
||||
self.pulse_period = pulse_period != nil ? pulse_period : 1000
|
||||
self.current_pulse_opacity = self.max_opacity
|
||||
|
||||
# Register parameters with validation
|
||||
self.register_param("base_pattern", {"default": nil})
|
||||
self.register_param("min_opacity", {"min": 0, "max": 255, "default": 0})
|
||||
self.register_param("max_opacity", {"min": 0, "max": 255, "default": 255})
|
||||
self.register_param("pulse_period", {"min": 100, "default": 1000})
|
||||
|
||||
# Set initial parameter values
|
||||
self.set_param("base_pattern", self.base_pattern)
|
||||
self.set_param("min_opacity", self.min_opacity)
|
||||
self.set_param("max_opacity", self.max_opacity)
|
||||
self.set_param("pulse_period", self.pulse_period)
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "base_pattern"
|
||||
self.base_pattern = value
|
||||
elif name == "min_opacity"
|
||||
self.min_opacity = value
|
||||
elif name == "max_opacity"
|
||||
self.max_opacity = value
|
||||
elif name == "pulse_period"
|
||||
self.pulse_period = value
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Update the base pattern if it has an update method
|
||||
if self.base_pattern != nil && self.base_pattern.update != nil
|
||||
self.base_pattern.update(time_ms)
|
||||
end
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
# Calculate position in the pulse cycle (0 to 32767, representing 0 to 2π)
|
||||
var cycle_position = tasmota.scale_uint(elapsed % self.pulse_period, 0, self.pulse_period, 0, 32767)
|
||||
|
||||
# Use fixed-point sine to create smooth pulsing effect
|
||||
# tasmota.sine_int returns values from -4096 to 4096 (representing -1.0 to 1.0)
|
||||
# Convert to 0 to 1.0 range by adding 4096 and dividing by 8192
|
||||
var pulse_factor = tasmota.sine_int(cycle_position) + 4096 # range is 0..8192
|
||||
|
||||
# Calculate current pulse opacity based on min/max and pulse factor
|
||||
self.current_pulse_opacity = tasmota.scale_uint(pulse_factor, 0, 8192, self.min_opacity, self.max_opacity)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Get a color for a specific pixel position and time
|
||||
# This delegates to the base pattern but applies pulse opacity
|
||||
#
|
||||
# @param pixel: int - Pixel index (0-based)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def get_color_at(pixel, time_ms)
|
||||
if self.base_pattern == nil
|
||||
return 0x00000000 # Transparent if no base pattern
|
||||
end
|
||||
|
||||
# Get color from base pattern
|
||||
var base_color = self.base_pattern.get_color_at(pixel, time_ms)
|
||||
|
||||
# Apply pulse opacity to the base color
|
||||
var base_alpha = (base_color >> 24) & 0xFF
|
||||
var pulsed_alpha = tasmota.scale_uint(self.current_pulse_opacity, 0, 255, 0, base_alpha)
|
||||
|
||||
# Resolve and combine with base animation opacity
|
||||
var current_opacity = self.resolve_value(self.opacity, "opacity", time_ms)
|
||||
var final_alpha = tasmota.scale_uint(current_opacity, 0, 255, 0, pulsed_alpha)
|
||||
|
||||
# Return color with modified alpha
|
||||
return (base_color & 0x00FFFFFF) | (final_alpha << 24)
|
||||
end
|
||||
|
||||
# Render the pulsing pattern to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil || self.base_pattern == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
self.update(time_ms)
|
||||
|
||||
# Let the base pattern render first
|
||||
var modified = self.base_pattern.render(frame, time_ms)
|
||||
|
||||
# Apply pulse opacity to the entire frame
|
||||
if modified && self.current_pulse_opacity < 255
|
||||
frame.apply_brightness(self.current_pulse_opacity)
|
||||
end
|
||||
|
||||
# Resolve and apply base animation opacity if not full
|
||||
var current_opacity = self.resolve_value(self.opacity, "opacity", time_ms)
|
||||
if modified && current_opacity < 255
|
||||
frame.apply_brightness(current_opacity)
|
||||
end
|
||||
|
||||
return modified
|
||||
end
|
||||
|
||||
# Set the base pattern
|
||||
#
|
||||
# @param pattern: Pattern - The pattern to pulse
|
||||
# @return self for method chaining
|
||||
def set_base_pattern(pattern)
|
||||
self.set_param("base_pattern", pattern)
|
||||
return self
|
||||
end
|
||||
|
||||
# Set the minimum opacity
|
||||
#
|
||||
# @param opacity: int - Minimum opacity level (0-255)
|
||||
# @return self for method chaining
|
||||
def set_min_opacity(opacity)
|
||||
self.set_param("min_opacity", opacity)
|
||||
return self
|
||||
end
|
||||
|
||||
# Set the maximum opacity
|
||||
#
|
||||
# @param opacity: int - Maximum opacity level (0-255)
|
||||
# @return self for method chaining
|
||||
def set_max_opacity(opacity)
|
||||
self.set_param("max_opacity", opacity)
|
||||
return self
|
||||
end
|
||||
|
||||
# Set the pulse period
|
||||
#
|
||||
# @param period: int - Time for one complete pulse cycle in milliseconds
|
||||
# @return self for method chaining
|
||||
def set_pulse_period(period)
|
||||
self.set_param("pulse_period", period)
|
||||
return self
|
||||
end
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"PulseAnimation(base_pattern={self.base_pattern}, min_opacity={self.min_opacity}, max_opacity={self.max_opacity}, pulse_period={self.pulse_period}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory function to create a pulse animation
|
||||
#
|
||||
# @param base_pattern: Pattern - The pattern to pulse
|
||||
# @param min_opacity: int - Minimum opacity level (0-255), defaults to 0
|
||||
# @param max_opacity: int - Maximum opacity level (0-255), defaults to 255
|
||||
# @param pulse_period: int - Time for one complete pulse cycle in milliseconds, defaults to 1000
|
||||
# @param priority: int - Rendering priority (higher = on top), defaults to 0
|
||||
# @param duration: int - Duration in milliseconds, 0 for infinite, defaults to 0
|
||||
# @param loop: bool - Whether animation should loop when duration is reached, defaults to true
|
||||
# @param opacity: int - Base animation opacity (0-255), defaults to 255
|
||||
# @param name: string - Optional name for the animation
|
||||
# @return PulseAnimation - A new pulse animation instance
|
||||
def pulse(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
|
||||
return PulseAnimation(base_pattern, min_opacity, max_opacity, pulse_period, priority, duration, loop, opacity, name)
|
||||
end
|
||||
|
||||
return {'pulse_animation': PulseAnimation, 'pulse': pulse}
|
||||
@ -0,0 +1,80 @@
|
||||
# RichPaletteAnimation - Animation with integrated rich palette color provider
|
||||
#
|
||||
# This animation class provides direct access to rich palette parameters,
|
||||
# forwarding them to an internal RichPaletteColorProvider instance.
|
||||
# This creates a cleaner API where users can set palette parameters directly
|
||||
# on the animation instead of accessing nested color provider properties.
|
||||
#
|
||||
# Follows the parameterized class specification with parameter forwarding pattern.
|
||||
|
||||
#@ solidify:RichPaletteAnimation,weak
|
||||
class RichPaletteAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var color_provider # Internal RichPaletteColorProvider instance
|
||||
|
||||
# Parameter definitions - only RichPaletteColorProvider parameters (Animation params inherited)
|
||||
static var PARAMS = {
|
||||
# RichPaletteColorProvider parameters (forwarded to internal provider)
|
||||
"palette": {"type": "instance", "default": nil},
|
||||
"cycle_period": {"min": 0, "default": 5000},
|
||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
||||
"brightness": {"min": 0, "max": 255, "default": 255},
|
||||
"range_min": {"default": 0},
|
||||
"range_max": {"default": 100}
|
||||
}
|
||||
|
||||
# Initialize a new RichPaletteAnimation
|
||||
#
|
||||
# @param engine: AnimationEngine - Reference to the animation engine (required)
|
||||
def init(engine)
|
||||
super(self).init(engine) # Initialize Animation base class
|
||||
|
||||
# Set default name (override inherited default)
|
||||
self.name = "rich_palette"
|
||||
|
||||
# Create internal RichPaletteColorProvider instance
|
||||
self.color_provider = animation.rich_palette(engine)
|
||||
|
||||
# Set the color parameter to our internal provider
|
||||
# Use direct values assignment to avoid triggering on_param_changed
|
||||
self.values["color"] = self.color_provider
|
||||
end
|
||||
|
||||
# Handle parameter changes - forward rich palette parameters to internal provider
|
||||
#
|
||||
# @param name: string - Name of the parameter that changed
|
||||
# @param value: any - New value of the parameter
|
||||
def on_param_changed(name, value)
|
||||
# Forward rich palette parameters to internal color provider
|
||||
if name == "palette" || name == "cycle_period" || name == "transition_type" ||
|
||||
name == "brightness" || name == "range_min" || name == "range_max"
|
||||
# Set parameter on internal color provider
|
||||
self.color_provider.set_param(name, value)
|
||||
else
|
||||
# Let parent handle animation-specific parameters
|
||||
super(self).on_param_changed(name, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Override start to ensure color provider is synchronized
|
||||
#
|
||||
# @param start_time: int - Optional start time in milliseconds
|
||||
# @return self for method chaining
|
||||
def start(start_time)
|
||||
# Call parent start method
|
||||
super(self).start(start_time)
|
||||
self.color_provider.start(start_time)
|
||||
return self
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
try
|
||||
return f"RichPaletteAnimation({self.name}, cycle_period={self.cycle_period}, brightness={self.brightness})"
|
||||
except ..
|
||||
return "RichPaletteAnimation(uninitialized)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {'rich_palette_animation': RichPaletteAnimation}
|
||||
292
lib/libesp32/berry_animation/src/animations/scale.be
Normal file
292
lib/libesp32/berry_animation/src/animations/scale.be
Normal file
@ -0,0 +1,292 @@
|
||||
# Scale animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation scales patterns up or down with configurable scaling factors,
|
||||
# interpolation methods, and center points.
|
||||
|
||||
#@ solidify:ScaleAnimation,weak
|
||||
class ScaleAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var scale_phase # Current phase for animated scaling
|
||||
var source_frame # Frame buffer for source animation
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var start_time # Animation start time
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"source_animation": {"type": "instance", "default": nil},
|
||||
"scale_factor": {"min": 1, "max": 255, "default": 128},
|
||||
"scale_speed": {"min": 0, "max": 255, "default": 0},
|
||||
"scale_mode": {"min": 0, "max": 3, "default": 0},
|
||||
"scale_center": {"min": 0, "max": 255, "default": 128},
|
||||
"interpolation": {"min": 0, "max": 1, "default": 1}
|
||||
}
|
||||
|
||||
# Initialize a new Scale animation
|
||||
# @param engine: AnimationEngine - Required animation engine
|
||||
def init(engine)
|
||||
# Call parent constructor with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.scale_phase = 0
|
||||
self.start_time = self.engine.time_ms
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Initialize frame buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
self.source_frame = animation.frame_buffer(current_strip_length)
|
||||
self.current_colors = []
|
||||
self.current_colors.resize(current_strip_length)
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
# No special handling needed for most parameters
|
||||
# Buffers are managed through engine strip length changes
|
||||
end
|
||||
|
||||
# Start/restart the animation
|
||||
def start(time_ms)
|
||||
# Call parent start first (handles ValueProvider propagation)
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Reset scale phase for animated modes
|
||||
self.scale_phase = 0
|
||||
|
||||
# Initialize timing
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
self.start_time = time_ms
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var current_scale_speed = self.scale_speed
|
||||
var current_scale_mode = self.scale_mode
|
||||
var current_source_animation = self.source_animation
|
||||
|
||||
# Update scale phase for animated modes
|
||||
if current_scale_speed > 0 && current_scale_mode > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Speed: 0-255 maps to 0-2 cycles per second
|
||||
var cycles_per_second = tasmota.scale_uint(current_scale_speed, 0, 255, 0, 2)
|
||||
if cycles_per_second > 0
|
||||
self.scale_phase = (elapsed * cycles_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Update source animation if it exists
|
||||
if current_source_animation != nil
|
||||
if !current_source_animation.is_running
|
||||
current_source_animation.start(self.start_time)
|
||||
end
|
||||
current_source_animation.update(time_ms)
|
||||
end
|
||||
|
||||
# Calculate scaled colors
|
||||
self._calculate_scale()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate current scale factor based on mode
|
||||
def _get_current_scale_factor()
|
||||
var current_scale_mode = self.scale_mode
|
||||
var current_scale_factor = self.scale_factor
|
||||
|
||||
if current_scale_mode == 0
|
||||
# Static scale
|
||||
return current_scale_factor
|
||||
elif current_scale_mode == 1
|
||||
# Oscillate between 0.5x and 2.0x
|
||||
var sine_val = self._sine(self.scale_phase)
|
||||
return tasmota.scale_uint(sine_val, 0, 255, 64, 255) # 0.5x to 2.0x
|
||||
elif current_scale_mode == 2
|
||||
# Grow from 0.5x to 2.0x
|
||||
return tasmota.scale_uint(self.scale_phase, 0, 255, 64, 255)
|
||||
else
|
||||
# Shrink from 2.0x to 0.5x
|
||||
return tasmota.scale_uint(255 - self.scale_phase, 0, 255, 64, 255)
|
||||
end
|
||||
end
|
||||
|
||||
# Simple sine approximation
|
||||
def _sine(angle)
|
||||
# Simple sine approximation using quarter-wave symmetry
|
||||
var quarter = angle % 64
|
||||
if angle < 64
|
||||
return tasmota.scale_uint(quarter, 0, 64, 128, 255)
|
||||
elif angle < 128
|
||||
return tasmota.scale_uint(128 - angle, 0, 64, 128, 255)
|
||||
elif angle < 192
|
||||
return tasmota.scale_uint(angle - 128, 0, 64, 128, 0)
|
||||
else
|
||||
return tasmota.scale_uint(256 - angle, 0, 64, 128, 0)
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate scaled colors for all pixels
|
||||
def _calculate_scale()
|
||||
# Get current strip length from engine
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure buffers are properly sized
|
||||
if size(self.current_colors) != current_strip_length
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Cache parameter values for performance
|
||||
var current_source_animation = self.source_animation
|
||||
var current_scale_center = self.scale_center
|
||||
var current_interpolation = self.interpolation
|
||||
|
||||
# Clear source frame
|
||||
self.source_frame.clear()
|
||||
|
||||
# Render source animation to frame
|
||||
if current_source_animation != nil
|
||||
current_source_animation.render(self.source_frame, 0)
|
||||
end
|
||||
|
||||
# Get current scale factor
|
||||
var current_scale = self._get_current_scale_factor()
|
||||
|
||||
# Calculate scale center in pixels
|
||||
var center_pixel = tasmota.scale_uint(current_scale_center, 0, 255, 0, current_strip_length - 1)
|
||||
|
||||
# Apply scaling transformation
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
# Calculate source position
|
||||
var distance_from_center = i - center_pixel
|
||||
# Scale: 128 = 1.0x, 64 = 0.5x, 255 = 2.0x
|
||||
var scaled_distance = tasmota.scale_uint(distance_from_center * 128, 0, 128 * 128, 0, current_scale * 128) / 128
|
||||
var source_pos = center_pixel + scaled_distance
|
||||
|
||||
if current_interpolation == 0
|
||||
# Nearest neighbor
|
||||
if source_pos >= 0 && source_pos < current_strip_length
|
||||
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
|
||||
else
|
||||
self.current_colors[i] = 0xFF000000
|
||||
end
|
||||
else
|
||||
# Linear interpolation using integer math
|
||||
if source_pos >= 0 && source_pos < current_strip_length - 1
|
||||
var pos_floor = int(source_pos)
|
||||
# Use integer fraction (0-255)
|
||||
var pos_frac_256 = int((source_pos - pos_floor) * 256)
|
||||
|
||||
if pos_floor >= 0 && pos_floor < current_strip_length - 1
|
||||
var color1 = self.source_frame.get_pixel_color(pos_floor)
|
||||
var color2 = self.source_frame.get_pixel_color(pos_floor + 1)
|
||||
self.current_colors[i] = self._interpolate_colors(color1, color2, pos_frac_256)
|
||||
else
|
||||
self.current_colors[i] = 0xFF000000
|
||||
end
|
||||
else
|
||||
self.current_colors[i] = 0xFF000000
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Interpolate between two colors using integer math
|
||||
def _interpolate_colors(color1, color2, factor_256)
|
||||
if factor_256 <= 0
|
||||
return color1
|
||||
elif factor_256 >= 256
|
||||
return color2
|
||||
end
|
||||
|
||||
# Extract ARGB components
|
||||
var a1 = (color1 >> 24) & 0xFF
|
||||
var r1 = (color1 >> 16) & 0xFF
|
||||
var g1 = (color1 >> 8) & 0xFF
|
||||
var b1 = color1 & 0xFF
|
||||
|
||||
var a2 = (color2 >> 24) & 0xFF
|
||||
var r2 = (color2 >> 16) & 0xFF
|
||||
var g2 = (color2 >> 8) & 0xFF
|
||||
var b2 = color2 & 0xFF
|
||||
|
||||
# Interpolate each component using integer math
|
||||
var a = a1 + ((a2 - a1) * factor_256 / 256)
|
||||
var r = r1 + ((r2 - r1) * factor_256 / 256)
|
||||
var g = g1 + ((g2 - g1) * factor_256 / 256)
|
||||
var b = b1 + ((b2 - b1) * factor_256 / 256)
|
||||
|
||||
return (a << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
|
||||
# Render scale to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var mode_names = ["static", "oscillate", "grow", "shrink"]
|
||||
var current_scale_mode = self.scale_mode
|
||||
var current_scale_factor = self.scale_factor
|
||||
var current_scale_speed = self.scale_speed
|
||||
var mode_name = mode_names[current_scale_mode] != nil ? mode_names[current_scale_mode] : "unknown"
|
||||
return f"ScaleAnimation({mode_name}, factor={current_scale_factor}, speed={current_scale_speed})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following parameterized class specification
|
||||
|
||||
# Create a static scale animation preset
|
||||
def scale_static(engine)
|
||||
var anim = animation.scale_animation(engine)
|
||||
anim.scale_mode = 0 # static mode
|
||||
anim.scale_speed = 0 # no animation
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create an oscillating scale animation preset
|
||||
def scale_oscillate(engine)
|
||||
var anim = animation.scale_animation(engine)
|
||||
anim.scale_mode = 1 # oscillate mode
|
||||
anim.scale_speed = 128 # medium speed
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a growing scale animation preset
|
||||
def scale_grow(engine)
|
||||
var anim = animation.scale_animation(engine)
|
||||
anim.scale_mode = 2 # grow mode
|
||||
anim.scale_speed = 128 # medium speed
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'scale_animation': ScaleAnimation, 'scale_static': scale_static, 'scale_oscillate': scale_oscillate, 'scale_grow': scale_grow}
|
||||
212
lib/libesp32/berry_animation/src/animations/shift.be
Normal file
212
lib/libesp32/berry_animation/src/animations/shift.be
Normal file
@ -0,0 +1,212 @@
|
||||
# Shift animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation shifts/scrolls patterns horizontally across the LED strip
|
||||
# with configurable speed, direction, and wrapping behavior.
|
||||
|
||||
#@ solidify:ShiftAnimation,weak
|
||||
class ShiftAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_offset # Current shift offset in 1/256th pixels
|
||||
var source_frame # Frame buffer for source animation
|
||||
var current_colors # Array of current colors for each pixel
|
||||
|
||||
# Parameter definitions with constraints
|
||||
static var PARAMS = {
|
||||
"source_animation": {"type": "instance", "default": nil},
|
||||
"shift_speed": {"min": 0, "max": 255, "default": 128},
|
||||
"direction": {"min": -1, "max": 1, "default": 1},
|
||||
"wrap_around": {"type": "bool", "default": true}
|
||||
}
|
||||
|
||||
# Initialize a new Shift animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.current_offset = 0
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Initialize buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
self.source_frame = animation.frame_buffer(current_strip_length)
|
||||
self.current_colors = []
|
||||
self.current_colors.resize(current_strip_length)
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
# Re-initialize buffers if strip length might have changed
|
||||
if name == "source_animation"
|
||||
self._initialize_buffers()
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var current_shift_speed = self.shift_speed
|
||||
var current_direction = self.direction
|
||||
var current_wrap_around = self.wrap_around
|
||||
var current_source_animation = self.source_animation
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Update shift offset based on speed
|
||||
if current_shift_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Speed: 0-255 maps to 0-10 pixels per second
|
||||
var pixels_per_second = tasmota.scale_uint(current_shift_speed, 0, 255, 0, 10 * 256)
|
||||
if pixels_per_second > 0
|
||||
var total_offset = (elapsed * pixels_per_second / 1000) * current_direction
|
||||
if current_wrap_around
|
||||
self.current_offset = total_offset % (current_strip_length * 256)
|
||||
if self.current_offset < 0
|
||||
self.current_offset += current_strip_length * 256
|
||||
end
|
||||
else
|
||||
self.current_offset = total_offset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update source animation if it exists
|
||||
if current_source_animation != nil
|
||||
if !current_source_animation.is_running
|
||||
current_source_animation.start(self.start_time)
|
||||
end
|
||||
current_source_animation.update(time_ms)
|
||||
end
|
||||
|
||||
# Calculate shifted colors
|
||||
self._calculate_shift()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate shifted colors for all pixels
|
||||
def _calculate_shift()
|
||||
# Get current strip length and ensure buffers are correct size
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
if size(self.current_colors) != current_strip_length
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Cache parameter values
|
||||
var current_source_animation = self.source_animation
|
||||
var current_wrap_around = self.wrap_around
|
||||
|
||||
# Clear source frame
|
||||
self.source_frame.clear()
|
||||
|
||||
# Render source animation to frame
|
||||
if current_source_animation != nil
|
||||
current_source_animation.render(self.source_frame, 0)
|
||||
end
|
||||
|
||||
# Apply shift transformation
|
||||
var pixel_offset = self.current_offset / 256 # Convert to pixel units
|
||||
var sub_pixel_offset = self.current_offset % 256 # Sub-pixel remainder
|
||||
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
var source_pos = i - pixel_offset
|
||||
|
||||
if current_wrap_around
|
||||
# Wrap source position
|
||||
while source_pos < 0
|
||||
source_pos += current_strip_length
|
||||
end
|
||||
while source_pos >= current_strip_length
|
||||
source_pos -= current_strip_length
|
||||
end
|
||||
|
||||
# Get color from wrapped position
|
||||
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
|
||||
else
|
||||
# Clamp to strip bounds
|
||||
if source_pos >= 0 && source_pos < current_strip_length
|
||||
self.current_colors[i] = self.source_frame.get_pixel_color(source_pos)
|
||||
else
|
||||
self.current_colors[i] = 0xFF000000 # Black for out-of-bounds
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render shift to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var current_direction = self.direction
|
||||
var current_shift_speed = self.shift_speed
|
||||
var current_wrap_around = self.wrap_around
|
||||
var current_priority = self.priority
|
||||
var dir_str = current_direction > 0 ? "right" : "left"
|
||||
return f"ShiftAnimation({dir_str}, speed={current_shift_speed}, wrap={current_wrap_around}, priority={current_priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions
|
||||
|
||||
# Create a shift animation that scrolls right
|
||||
def shift_scroll_right(engine)
|
||||
var anim = animation.shift_animation(engine)
|
||||
anim.direction = 1
|
||||
anim.shift_speed = 128
|
||||
anim.wrap_around = true
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a shift animation that scrolls left
|
||||
def shift_scroll_left(engine)
|
||||
var anim = animation.shift_animation(engine)
|
||||
anim.direction = -1
|
||||
anim.shift_speed = 128
|
||||
anim.wrap_around = true
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a fast scrolling shift animation
|
||||
def shift_fast_scroll(engine)
|
||||
var anim = animation.shift_animation(engine)
|
||||
anim.direction = 1
|
||||
anim.shift_speed = 200
|
||||
anim.wrap_around = true
|
||||
return anim
|
||||
end
|
||||
|
||||
return {
|
||||
'shift_animation': ShiftAnimation,
|
||||
'shift_scroll_right': shift_scroll_right,
|
||||
'shift_scroll_left': shift_scroll_left,
|
||||
'shift_fast_scroll': shift_fast_scroll
|
||||
}
|
||||
18
lib/libesp32/berry_animation/src/animations/solid.be
Normal file
18
lib/libesp32/berry_animation/src/animations/solid.be
Normal file
@ -0,0 +1,18 @@
|
||||
# Solid Animation Factory
|
||||
# Creates a solid color animation using the base Animation class
|
||||
# Follows the parameterized class specification with engine-only pattern
|
||||
|
||||
# Factory function to create a solid animation
|
||||
# Following the "Engine-only factory functions" pattern from the specification
|
||||
#
|
||||
# @param engine: AnimationEngine - Required engine parameter (only parameter)
|
||||
# @return Animation - A new solid animation instance with default parameters
|
||||
def solid(engine)
|
||||
# Create animation with engine-only constructor
|
||||
var anim = animation.animation(engine)
|
||||
anim.name = "solid"
|
||||
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'solid': solid}
|
||||
256
lib/libesp32/berry_animation/src/animations/sparkle.be
Normal file
256
lib/libesp32/berry_animation/src/animations/sparkle.be
Normal file
@ -0,0 +1,256 @@
|
||||
# Sparkle animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates random sparkles that appear and fade out over time,
|
||||
# with configurable density, fade speed, and colors.
|
||||
|
||||
#@ solidify:SparkleAnimation,weak
|
||||
class SparkleAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var sparkle_states # Array of sparkle states for each pixel
|
||||
var sparkle_ages # Array of sparkle ages for each pixel
|
||||
var random_seed # Seed for random number generation
|
||||
var last_update # Last update time for frame timing
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"density": {"min": 0, "max": 255, "default": 30},
|
||||
"fade_speed": {"min": 0, "max": 255, "default": 50},
|
||||
"sparkle_duration": {"min": 0, "max": 255, "default": 60},
|
||||
"min_brightness": {"min": 0, "max": 255, "default": 100},
|
||||
"max_brightness": {"min": 0, "max": 255, "default": 255}
|
||||
}
|
||||
|
||||
# Initialize a new Sparkle animation
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize random seed using engine time
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
# Initialize arrays and state - will be sized when strip length is known
|
||||
self.current_colors = []
|
||||
self.sparkle_states = [] # 0 = off, 1-255 = brightness
|
||||
self.sparkle_ages = [] # Age of each sparkle
|
||||
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize buffers based on engine strip length
|
||||
self._initialize_buffers()
|
||||
end
|
||||
|
||||
# Simple pseudo-random number generator
|
||||
def _random()
|
||||
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
return self.random_seed
|
||||
end
|
||||
|
||||
# Get random number in range [0, max)
|
||||
def _random_range(max)
|
||||
if max <= 0
|
||||
return 0
|
||||
end
|
||||
return self._random() % max
|
||||
end
|
||||
|
||||
# Initialize buffers based on current strip length
|
||||
def _initialize_buffers()
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
|
||||
self.current_colors.resize(current_strip_length)
|
||||
self.sparkle_states.resize(current_strip_length)
|
||||
self.sparkle_ages.resize(current_strip_length)
|
||||
|
||||
# Initialize all pixels
|
||||
var back_color = self.back_color
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
self.current_colors[i] = back_color
|
||||
self.sparkle_states[i] = 0
|
||||
self.sparkle_ages[i] = 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Override start method for timing control (acts as both start and restart)
|
||||
def start(time_ms)
|
||||
# Call parent start first (handles ValueProvider propagation)
|
||||
super(self).start(time_ms)
|
||||
|
||||
# Reset random seed for consistent restarts
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
# Reinitialize buffers in case strip length changed
|
||||
self._initialize_buffers()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update at approximately 30 FPS
|
||||
var update_interval = 33 # ~30 FPS
|
||||
if time_ms - self.last_update < update_interval
|
||||
return true
|
||||
end
|
||||
self.last_update = time_ms
|
||||
|
||||
# Update sparkle simulation
|
||||
self._update_sparkles(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update sparkle states and create new sparkles
|
||||
def _update_sparkles(time_ms)
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Cache parameter values for performance
|
||||
var sparkle_duration = self.sparkle_duration
|
||||
var fade_speed = self.fade_speed
|
||||
var density = self.density
|
||||
var min_brightness = self.min_brightness
|
||||
var max_brightness = self.max_brightness
|
||||
var back_color = self.back_color
|
||||
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
# Update existing sparkles
|
||||
if self.sparkle_states[i] > 0
|
||||
self.sparkle_ages[i] += 1
|
||||
|
||||
# Check if sparkle should fade or die
|
||||
if self.sparkle_ages[i] >= sparkle_duration
|
||||
# Sparkle has reached end of life
|
||||
self.sparkle_states[i] = 0
|
||||
self.sparkle_ages[i] = 0
|
||||
self.current_colors[i] = back_color
|
||||
else
|
||||
# Fade sparkle based on age and fade speed
|
||||
var age_ratio = tasmota.scale_uint(self.sparkle_ages[i], 0, sparkle_duration, 0, 255)
|
||||
var fade_factor = 255 - tasmota.scale_uint(age_ratio, 0, 255, 0, fade_speed)
|
||||
|
||||
# Apply fade to brightness
|
||||
var new_brightness = tasmota.scale_uint(self.sparkle_states[i], 0, 255, 0, fade_factor)
|
||||
if new_brightness < 10
|
||||
# Sparkle too dim, turn off
|
||||
self.sparkle_states[i] = 0
|
||||
self.sparkle_ages[i] = 0
|
||||
self.current_colors[i] = back_color
|
||||
else
|
||||
# Update sparkle color with new brightness
|
||||
self._update_sparkle_color(i, new_brightness, time_ms)
|
||||
end
|
||||
end
|
||||
else
|
||||
# Check if new sparkle should appear
|
||||
if self._random_range(256) < density
|
||||
# Create new sparkle
|
||||
var brightness = min_brightness + self._random_range(max_brightness - min_brightness + 1)
|
||||
self.sparkle_states[i] = brightness
|
||||
self.sparkle_ages[i] = 0
|
||||
self._update_sparkle_color(i, brightness, time_ms)
|
||||
else
|
||||
# No sparkle, use background color
|
||||
self.current_colors[i] = back_color
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Update color for a specific sparkle
|
||||
def _update_sparkle_color(pixel, brightness, time_ms)
|
||||
# Get base color using virtual parameter access
|
||||
var base_color = 0xFFFFFFFF
|
||||
|
||||
# Access color parameter (automatically resolves ValueProviders)
|
||||
var color_param = self.color
|
||||
if animation.is_color_provider(color_param) && color_param.get_color_for_value != nil
|
||||
base_color = color_param.get_color_for_value(brightness, 0)
|
||||
else
|
||||
# Use the resolved color value with pixel influence for variation
|
||||
base_color = self.get_param_value("color", time_ms + pixel * 10)
|
||||
end
|
||||
|
||||
# Apply brightness scaling
|
||||
var a = (base_color >> 24) & 0xFF
|
||||
var r = (base_color >> 16) & 0xFF
|
||||
var g = (base_color >> 8) & 0xFF
|
||||
var b = base_color & 0xFF
|
||||
|
||||
r = tasmota.scale_uint(brightness, 0, 255, 0, r)
|
||||
g = tasmota.scale_uint(brightness, 0, 255, 0, g)
|
||||
b = tasmota.scale_uint(brightness, 0, 255, 0, b)
|
||||
|
||||
self.current_colors[pixel] = (a << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
|
||||
# Render sparkles to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var color_param = self.get_param("color")
|
||||
var color_str
|
||||
if animation.is_value_provider(color_param)
|
||||
color_str = str(color_param)
|
||||
else
|
||||
color_str = f"0x{self.color :08x}"
|
||||
end
|
||||
return f"SparkleAnimation(color={color_str}, density={self.density}, fade_speed={self.fade_speed}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following parameterized class specification
|
||||
|
||||
# Create a white sparkle animation preset
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
# @return SparkleAnimation - A new white sparkle animation instance
|
||||
def sparkle_white(engine)
|
||||
var anim = animation.sparkle_animation(engine)
|
||||
anim.color = 0xFFFFFFFF # white sparkles
|
||||
anim.name = "sparkle_white"
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a rainbow sparkle animation preset
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
# @return SparkleAnimation - A new rainbow sparkle animation instance
|
||||
def sparkle_rainbow(engine)
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1 # sine transition
|
||||
|
||||
var anim = animation.sparkle_animation(engine)
|
||||
anim.color = rainbow_provider
|
||||
anim.name = "sparkle_rainbow"
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'sparkle_animation': SparkleAnimation, 'sparkle_white': sparkle_white, 'sparkle_rainbow': sparkle_rainbow}
|
||||
338
lib/libesp32/berry_animation/src/animations/twinkle.be
Normal file
338
lib/libesp32/berry_animation/src/animations/twinkle.be
Normal file
@ -0,0 +1,338 @@
|
||||
# Twinkle animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates a twinkling stars effect with random lights
|
||||
# appearing and fading at different positions with customizable density and timing.
|
||||
|
||||
#@ solidify:TwinkleAnimation,weak
|
||||
class TwinkleAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Non-parameter instance variables only
|
||||
var twinkle_states # Array storing twinkle state for each pixel
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var last_update # Last update time for timing
|
||||
var random_seed # Seed for random number generation
|
||||
|
||||
# Parameter definitions with constraints
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFFFFFF},
|
||||
"density": {"min": 0, "max": 255, "default": 128},
|
||||
"twinkle_speed": {"min": 1, "max": 5000, "default": 6},
|
||||
"fade_speed": {"min": 0, "max": 255, "default": 180},
|
||||
"min_brightness": {"min": 0, "max": 255, "default": 32},
|
||||
"max_brightness": {"min": 0, "max": 255, "default": 255}
|
||||
}
|
||||
|
||||
# Initialize a new Twinkle animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (REQUIRED)
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.twinkle_states = []
|
||||
self.current_colors = []
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize random seed using engine time
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
# Initialize arrays based on strip length from engine
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
# Initialize arrays based on current strip length
|
||||
def _initialize_arrays()
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize arrays
|
||||
self.twinkle_states.resize(strip_length)
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Initialize all pixels to off state
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.twinkle_states[i] = 0 # 0 = off, >0 = brightness level
|
||||
self.current_colors[i] = 0x00000000 # Transparent (alpha = 0)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "twinkle_speed"
|
||||
# Handle twinkle_speed - can be Hz (1-20) or period in ms (50-5000)
|
||||
if value >= 50 # Assume it's period in milliseconds
|
||||
# Convert period (ms) to frequency (Hz): Hz = 1000 / ms
|
||||
# Clamp to reasonable range 1-20 Hz
|
||||
var hz = 1000 / value
|
||||
if hz < 1
|
||||
hz = 1
|
||||
elif hz > 20
|
||||
hz = 20
|
||||
end
|
||||
# Update the parameter with the converted value
|
||||
self.set_param("twinkle_speed", hz)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Simple pseudo-random number generator
|
||||
# Uses a linear congruential generator for consistent results
|
||||
def _random()
|
||||
self.random_seed = (self.random_seed * 1103515245 + 12345) & 0x7FFFFFFF
|
||||
return self.random_seed
|
||||
end
|
||||
|
||||
# Get random number in range [0, max)
|
||||
def _random_range(max)
|
||||
if max <= 0
|
||||
return 0
|
||||
end
|
||||
return self._random() % max
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
# Access parameters via virtual members
|
||||
var twinkle_speed = self.twinkle_speed
|
||||
|
||||
# Check if it's time to update the twinkle simulation
|
||||
# Update frequency is based on twinkle_speed (Hz)
|
||||
var update_interval = 1000 / twinkle_speed # milliseconds between updates
|
||||
if time_ms - self.last_update >= update_interval
|
||||
self.last_update = time_ms
|
||||
self._update_twinkle_simulation(time_ms)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update the twinkle simulation with alpha-based fading
|
||||
def _update_twinkle_simulation(time_ms)
|
||||
# Access parameters via virtual members (cache for performance)
|
||||
var fade_speed = self.fade_speed
|
||||
var density = self.density
|
||||
var min_brightness = self.min_brightness
|
||||
var max_brightness = self.max_brightness
|
||||
var color = self.color
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
# Step 1: Fade existing twinkles by reducing alpha
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
var current_color = self.current_colors[i]
|
||||
var alpha = (current_color >> 24) & 0xFF
|
||||
|
||||
if alpha > 0
|
||||
# Calculate fade amount based on fade_speed
|
||||
var fade_amount = tasmota.scale_uint(fade_speed, 0, 255, 1, 20)
|
||||
if alpha <= fade_amount
|
||||
# Star has faded completely - reset to transparent
|
||||
self.twinkle_states[i] = 0
|
||||
self.current_colors[i] = 0x00000000
|
||||
else
|
||||
# Reduce alpha while keeping RGB components unchanged
|
||||
var new_alpha = alpha - fade_amount
|
||||
var rgb = current_color & 0x00FFFFFF # Keep RGB, clear alpha
|
||||
self.current_colors[i] = (new_alpha << 24) | rgb
|
||||
end
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Step 2: Randomly create new twinkles based on density
|
||||
# For each pixel, check if it should twinkle based on density probability
|
||||
var j = 0
|
||||
while j < strip_length
|
||||
# Only consider pixels that are currently off (transparent)
|
||||
if self.twinkle_states[j] == 0
|
||||
# Use density as probability out of 255
|
||||
if self._random_range(255) < density
|
||||
# Create new star at full brightness with random intensity alpha
|
||||
var star_alpha = min_brightness + self._random_range(max_brightness - min_brightness + 1)
|
||||
|
||||
# Get base color (automatically resolves ValueProviders)
|
||||
var base_color = color
|
||||
|
||||
# Extract RGB components (ignore original alpha)
|
||||
var r = (base_color >> 16) & 0xFF
|
||||
var g = (base_color >> 8) & 0xFF
|
||||
var b = base_color & 0xFF
|
||||
|
||||
# Create new star with full-brightness color and variable alpha
|
||||
self.twinkle_states[j] = 1 # Mark as active (non-zero)
|
||||
self.current_colors[j] = (star_alpha << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
end
|
||||
j += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render the twinkle to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
# Only render pixels that are actually twinkling (non-transparent)
|
||||
var modified = false
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
var color = self.current_colors[i]
|
||||
# Only set pixels that have some alpha (are visible)
|
||||
if (color >> 24) & 0xFF > 0
|
||||
frame.set_pixel_color(i, color)
|
||||
modified = true
|
||||
end
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return modified
|
||||
end
|
||||
|
||||
# NO setter/getter methods - use direct assignment instead:
|
||||
# obj.color = value
|
||||
# obj.density = value
|
||||
# obj.twinkle_speed = value
|
||||
# obj.fade_speed = value
|
||||
# obj.min_brightness = value
|
||||
# obj.max_brightness = value
|
||||
|
||||
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
var color_str
|
||||
var raw_color = self.get_param("color")
|
||||
if animation.is_value_provider(raw_color)
|
||||
color_str = str(raw_color)
|
||||
else
|
||||
color_str = f"0x{self.color :08x}"
|
||||
end
|
||||
return f"TwinkleAnimation(color={color_str}, density={self.density}, twinkle_speed={self.twinkle_speed}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory function to create a classic white twinkle animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return TwinkleAnimation - A new twinkle animation instance
|
||||
def twinkle_classic(engine)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = 0xFFFFFFFF
|
||||
anim.density = 150
|
||||
anim.twinkle_speed = 6
|
||||
anim.fade_speed = 180
|
||||
anim.min_brightness = 32
|
||||
anim.max_brightness = 255
|
||||
return anim
|
||||
end
|
||||
|
||||
# Factory function to create a colored twinkle animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return TwinkleAnimation - A new twinkle animation instance
|
||||
def twinkle_solid(engine)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = 0xFF0080FF # Blue
|
||||
anim.density = 100
|
||||
anim.twinkle_speed = 6
|
||||
anim.fade_speed = 180
|
||||
anim.min_brightness = 32
|
||||
anim.max_brightness = 255
|
||||
return anim
|
||||
end
|
||||
|
||||
# Factory function to create a rainbow twinkle animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return TwinkleAnimation - A new twinkle animation instance
|
||||
def twinkle_rainbow(engine)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
# TODO: Set up rainbow color provider when available
|
||||
anim.color = 0xFFFFFFFF # White for now
|
||||
anim.density = 120
|
||||
anim.twinkle_speed = 6
|
||||
anim.fade_speed = 180
|
||||
anim.min_brightness = 32
|
||||
anim.max_brightness = 255
|
||||
return anim
|
||||
end
|
||||
|
||||
# Factory function to create a gentle twinkle animation (low density, slow fade)
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return TwinkleAnimation - A new twinkle animation instance
|
||||
def twinkle_gentle(engine)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = 0xFFFFD700 # Gold
|
||||
anim.density = 64
|
||||
anim.twinkle_speed = 3
|
||||
anim.fade_speed = 120
|
||||
anim.min_brightness = 16
|
||||
anim.max_brightness = 180
|
||||
return anim
|
||||
end
|
||||
|
||||
# Factory function to create an intense twinkle animation (high density, fast fade)
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return TwinkleAnimation - A new twinkle animation instance
|
||||
def twinkle_intense(engine)
|
||||
var anim = animation.twinkle_animation(engine)
|
||||
anim.color = 0xFFFF0000 # Red
|
||||
anim.density = 200
|
||||
anim.twinkle_speed = 12
|
||||
anim.fade_speed = 220
|
||||
anim.min_brightness = 64
|
||||
anim.max_brightness = 255
|
||||
return anim
|
||||
end
|
||||
|
||||
return {
|
||||
'twinkle_animation': TwinkleAnimation,
|
||||
'twinkle_classic': twinkle_classic,
|
||||
'twinkle_solid': twinkle_solid,
|
||||
'twinkle_rainbow': twinkle_rainbow,
|
||||
'twinkle_gentle': twinkle_gentle,
|
||||
'twinkle_intense': twinkle_intense
|
||||
}
|
||||
280
lib/libesp32/berry_animation/src/animations/wave.be
Normal file
280
lib/libesp32/berry_animation/src/animations/wave.be
Normal file
@ -0,0 +1,280 @@
|
||||
# Wave animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates various wave patterns (sine, triangle, square, sawtooth)
|
||||
# with configurable amplitude, frequency, phase, and movement speed.
|
||||
|
||||
#@ solidify:WaveAnimation,weak
|
||||
class WaveAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var time_offset # Current time offset for movement
|
||||
var wave_table # Pre-computed wave table for performance
|
||||
|
||||
# Parameter definitions for WaveAnimation
|
||||
static var PARAMS = {
|
||||
"color": {"default": 0xFFFF0000},
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"wave_type": {"min": 0, "max": 3, "default": 0},
|
||||
"amplitude": {"min": 0, "max": 255, "default": 128},
|
||||
"frequency": {"min": 0, "max": 255, "default": 32},
|
||||
"phase": {"min": 0, "max": 255, "default": 0},
|
||||
"wave_speed": {"min": 0, "max": 255, "default": 50},
|
||||
"center_level": {"min": 0, "max": 255, "default": 128}
|
||||
}
|
||||
|
||||
# Initialize a new Wave animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (required)
|
||||
def init(engine)
|
||||
# Call parent constructor
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.current_colors = []
|
||||
self.time_offset = 0
|
||||
self.wave_table = []
|
||||
|
||||
# Initialize wave table for performance
|
||||
self._init_wave_table()
|
||||
end
|
||||
|
||||
# Initialize wave lookup tables for performance
|
||||
def _init_wave_table()
|
||||
self.wave_table.resize(256)
|
||||
|
||||
var current_wave_type = self.wave_type
|
||||
|
||||
var i = 0
|
||||
while i < 256
|
||||
# Generate different wave types
|
||||
var value = 0
|
||||
|
||||
if current_wave_type == 0
|
||||
# Sine wave - using quarter-wave symmetry
|
||||
var quarter = i % 64
|
||||
if i < 64
|
||||
# First quarter: approximate sine
|
||||
value = tasmota.scale_uint(quarter, 0, 64, 128, 255)
|
||||
elif i < 128
|
||||
# Second quarter: mirror first quarter
|
||||
value = tasmota.scale_uint(128 - i, 0, 64, 128, 255)
|
||||
elif i < 192
|
||||
# Third quarter: negative first quarter
|
||||
value = tasmota.scale_uint(i - 128, 0, 64, 128, 0)
|
||||
else
|
||||
# Fourth quarter: negative second quarter
|
||||
value = tasmota.scale_uint(256 - i, 0, 64, 128, 0)
|
||||
end
|
||||
elif current_wave_type == 1
|
||||
# Triangle wave
|
||||
if i < 128
|
||||
value = tasmota.scale_uint(i, 0, 128, 0, 255)
|
||||
else
|
||||
value = tasmota.scale_uint(256 - i, 0, 128, 0, 255)
|
||||
end
|
||||
elif current_wave_type == 2
|
||||
# Square wave
|
||||
value = i < 128 ? 255 : 0
|
||||
else
|
||||
# Sawtooth wave
|
||||
value = i
|
||||
end
|
||||
|
||||
self.wave_table[i] = value
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
if name == "wave_type"
|
||||
self._init_wave_table() # Regenerate wave table when wave type changes
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Update time offset based on wave speed
|
||||
var current_wave_speed = self.wave_speed
|
||||
if current_wave_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Speed: 0-255 maps to 0-10 cycles per second
|
||||
var cycles_per_second = tasmota.scale_uint(current_wave_speed, 0, 255, 0, 10)
|
||||
if cycles_per_second > 0
|
||||
self.time_offset = (elapsed * cycles_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate wave colors
|
||||
self._calculate_wave(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate wave colors for all pixels
|
||||
def _calculate_wave(time_ms)
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var current_frequency = self.frequency
|
||||
var current_phase = self.phase
|
||||
var current_amplitude = self.amplitude
|
||||
var current_center_level = self.center_level
|
||||
var current_back_color = self.back_color
|
||||
var current_color = self.color
|
||||
|
||||
# Resize current_colors array if needed
|
||||
if self.current_colors.size() != strip_length
|
||||
self.current_colors.resize(strip_length)
|
||||
end
|
||||
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate wave position for this pixel
|
||||
var x = tasmota.scale_uint(i, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Apply frequency scaling and phase offset
|
||||
var wave_pos = ((x * current_frequency / 32) + current_phase + self.time_offset) & 255
|
||||
|
||||
# Get wave value from lookup table
|
||||
var wave_value = self.wave_table[wave_pos]
|
||||
|
||||
# Apply amplitude scaling around center level
|
||||
var scaled_amplitude = tasmota.scale_uint(current_amplitude, 0, 255, 0, 128)
|
||||
var final_value = 0
|
||||
|
||||
if wave_value >= 128
|
||||
# Upper half of wave
|
||||
var upper_amount = wave_value - 128
|
||||
upper_amount = tasmota.scale_uint(upper_amount, 0, 127, 0, scaled_amplitude)
|
||||
final_value = current_center_level + upper_amount
|
||||
else
|
||||
# Lower half of wave
|
||||
var lower_amount = 128 - wave_value
|
||||
lower_amount = tasmota.scale_uint(lower_amount, 0, 128, 0, scaled_amplitude)
|
||||
final_value = current_center_level - lower_amount
|
||||
end
|
||||
|
||||
# Clamp to valid range
|
||||
if final_value > 255
|
||||
final_value = 255
|
||||
elif final_value < 0
|
||||
final_value = 0
|
||||
end
|
||||
|
||||
# Get color from provider or use background
|
||||
var color = current_back_color
|
||||
if final_value > 10 # Threshold to avoid very dim colors
|
||||
# If the color is a provider that supports get_color_for_value, use it
|
||||
if animation.is_color_provider(current_color) && current_color.get_color_for_value != nil
|
||||
color = current_color.get_color_for_value(final_value, 0)
|
||||
else
|
||||
# Use resolve_value with wave influence
|
||||
color = self.resolve_value(current_color, "color", time_ms + final_value * 10)
|
||||
|
||||
# Apply wave intensity as brightness scaling
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
r = tasmota.scale_uint(final_value, 0, 255, 0, r)
|
||||
g = tasmota.scale_uint(final_value, 0, 255, 0, g)
|
||||
b = tasmota.scale_uint(final_value, 0, 255, 0, b)
|
||||
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Render wave to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width && i < self.current_colors.size()
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var wave_names = ["sine", "triangle", "square", "sawtooth"]
|
||||
var current_wave_type = self.wave_type
|
||||
var wave_name = wave_names[current_wave_type] != nil ? wave_names[current_wave_type] : "unknown"
|
||||
var current_color = self.color
|
||||
var color_str
|
||||
if animation.is_value_provider(current_color)
|
||||
color_str = str(current_color)
|
||||
else
|
||||
color_str = f"0x{current_color :08x}"
|
||||
end
|
||||
return f"WaveAnimation({wave_name}, color={color_str}, freq={self.frequency}, speed={self.wave_speed}, priority={self.priority}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions
|
||||
|
||||
# Create a rainbow sine wave animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return WaveAnimation - A new wave animation instance
|
||||
def wave_rainbow_sine(engine)
|
||||
var anim = animation.wave_animation(engine)
|
||||
# Set up rainbow color provider
|
||||
var rainbow_provider = animation.rich_palette(engine)
|
||||
rainbow_provider.palette = animation.PALETTE_RAINBOW
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1 # sine transition
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.set_range(0, 255)
|
||||
anim.color = rainbow_provider
|
||||
anim.wave_type = 0 # sine wave
|
||||
anim.frequency = 32
|
||||
anim.wave_speed = 50
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a single color sine wave animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return WaveAnimation - A new wave animation instance
|
||||
def wave_single_sine(engine)
|
||||
var anim = animation.wave_animation(engine)
|
||||
anim.color = 0xFFFF0000 # Default red color
|
||||
anim.wave_type = 0 # sine wave
|
||||
anim.frequency = 32
|
||||
anim.wave_speed = 50
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a custom wave animation
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine
|
||||
# @return WaveAnimation - A new wave animation instance
|
||||
def wave_custom(engine)
|
||||
var anim = animation.wave_animation(engine)
|
||||
anim.color = 0xFFFFFF00 # Default yellow color
|
||||
anim.wave_type = 2 # square wave
|
||||
anim.frequency = 40
|
||||
anim.wave_speed = 30
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'wave_animation': WaveAnimation, 'wave_rainbow_sine': wave_rainbow_sine, 'wave_single_sine': wave_single_sine, 'wave_custom': wave_custom}
|
||||
@ -27,4 +27,7 @@
|
||||
#include "be_mapping.h"
|
||||
|
||||
#include "solidify/solidified_animation.h"
|
||||
#ifdef USE_BERRY_ANIMATION_DSL
|
||||
#include "solidify/solidified_animation_dsl.h"
|
||||
#endif
|
||||
#endif
|
||||
@ -1,104 +1,125 @@
|
||||
# Animation base class
|
||||
# Defines the interface for all animations in the Berry Animation Framework
|
||||
#
|
||||
# Animation extends Pattern with temporal behavior - duration, looping, timing, etc.
|
||||
# This allows animations to be used anywhere patterns can be used, but with
|
||||
# additional time-based capabilities.
|
||||
# Animation base class - The unified root of the animation hierarchy
|
||||
#
|
||||
# An Animation defines WHAT should be displayed and HOW it changes over time.
|
||||
# Animations can generate colors for any pixel at any time, have priority for layering,
|
||||
# and can be rendered directly. They also support temporal behavior like duration and looping.
|
||||
#
|
||||
# This is the unified base class for all visual elements in the framework.
|
||||
# A Pattern is simply an Animation with infinite duration (duration = 0).
|
||||
|
||||
class Animation : animation.pattern
|
||||
# Animation-specific state variables (inherits priority, opacity, name, etc. from Pattern)
|
||||
class Animation : animation.parameterized_object
|
||||
# Non-parameter instance variables only
|
||||
var start_time # Time when animation started (ms) (int)
|
||||
var current_time # Current animation time (ms) (int)
|
||||
var duration # Total animation duration, 0 for infinite (ms) (int)
|
||||
var loop # Whether animation should loop (bool)
|
||||
|
||||
# Parameter definitions
|
||||
static var PARAMS = {
|
||||
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
||||
"is_running": {"type": "bool", "default": false}, # Whether the animation is active
|
||||
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
||||
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
||||
"loop": {"type": "bool", "default": true}, # Whether to loop when duration is reached
|
||||
"opacity": {"min": 0, "max": 255, "default": 255}, # Animation opacity/brightness (0-255)
|
||||
"color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB)
|
||||
}
|
||||
|
||||
# Initialize a new animation
|
||||
#
|
||||
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
|
||||
# @param duration: int - Duration in milliseconds, defaults to 0 (infinite) if nil
|
||||
# @param loop: bool - Whether animation should loop when duration is reached, defaults to false if nil
|
||||
# @param opacity: int - Animation opacity (0-255), defaults to 255 if nil
|
||||
# @param name: string - Optional name for the animation, defaults to "animation" if nil
|
||||
def init(priority, duration, loop, opacity, name)
|
||||
# Call parent Pattern constructor
|
||||
super(self).init(priority, opacity, name != nil ? name : "animation")
|
||||
# @param engine: AnimationEngine - Reference to the animation engine (required)
|
||||
def init(engine)
|
||||
# Initialize parameter system with engine
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize animation-specific properties
|
||||
# Initialize non-parameter instance variables
|
||||
self.start_time = 0
|
||||
self.current_time = 0
|
||||
self.duration = duration != nil ? duration : 0 # default infinite
|
||||
self.loop = loop != nil ? (loop ? 1 : 0) : 0
|
||||
|
||||
# Register animation-specific parameters
|
||||
self._register_param("duration", {"min": 0, "default": 0})
|
||||
self._register_param("loop", {"min": 0, "max": 1, "default": 0})
|
||||
|
||||
# Set initial values for animation parameters
|
||||
self.set_param("duration", self.duration)
|
||||
self.set_param("loop", self.loop)
|
||||
end
|
||||
|
||||
# Start the animation (override Pattern's start to add timing)
|
||||
# Start/restart the animation (make it active and reset timing)
|
||||
#
|
||||
# @param start_time: int - Optional start time in milliseconds
|
||||
# @return self for method chaining
|
||||
def start(start_time)
|
||||
if !self.is_running
|
||||
super(self).start() # Call Pattern's start method
|
||||
self.start_time = start_time != nil ? start_time : tasmota.millis()
|
||||
self.current_time = self.start_time
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# Pause the animation (keeps state but doesn't update)
|
||||
#
|
||||
# @return self for method chaining
|
||||
def pause()
|
||||
self.is_running = false
|
||||
return self
|
||||
end
|
||||
|
||||
# Resume the animation from where it was paused
|
||||
#
|
||||
# @return self for method chaining
|
||||
def resume()
|
||||
self.is_running = true
|
||||
return self
|
||||
end
|
||||
|
||||
# Reset the animation to its initial state
|
||||
#
|
||||
# @return self for method chaining
|
||||
def reset()
|
||||
self.start_time = tasmota.millis()
|
||||
# Set is_running directly in values map to avoid infinite loop
|
||||
self.values["is_running"] = true
|
||||
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
|
||||
self.start_time = actual_start_time
|
||||
self.current_time = self.start_time
|
||||
|
||||
# Start/restart all value providers in parameters
|
||||
self._start_value_providers(actual_start_time)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Update animation state based on current time (override Pattern's update)
|
||||
# Helper method to start/restart all value providers in parameters
|
||||
#
|
||||
# @param time_ms: int - Time to pass to value provider start methods
|
||||
def _start_value_providers(time_ms)
|
||||
# Iterate through all parameter values
|
||||
for param_value : self.values
|
||||
# Check if the parameter value is a value provider
|
||||
if animation.is_value_provider(param_value)
|
||||
# Call start method if it exists (acts as restart)
|
||||
try
|
||||
param_value.start(time_ms)
|
||||
except .. as e
|
||||
# Ignore errors if start method doesn't exist or fails
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes - specifically for is_running to control start/stop
|
||||
#
|
||||
# @param name: string - Parameter name that changed
|
||||
# @param value: any - New parameter value
|
||||
def on_param_changed(name, value)
|
||||
if name == "is_running"
|
||||
if value == true
|
||||
# Start the animation (but avoid infinite loop by not setting is_running again)
|
||||
var actual_start_time = self.engine.time_ms
|
||||
self.start_time = actual_start_time
|
||||
self.current_time = self.start_time
|
||||
# Start/restart all value providers in parameters
|
||||
self._start_value_providers(actual_start_time)
|
||||
elif value == false
|
||||
# Stop the animation - just set the internal state
|
||||
# (is_running is already set to false by the parameter system)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
# This method should be called regularly by the animation controller
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
if !self.is_running
|
||||
# Access is_running via virtual member
|
||||
var current_is_running = self.is_running
|
||||
if !current_is_running
|
||||
return false
|
||||
end
|
||||
|
||||
self.current_time = time_ms
|
||||
var elapsed = self.current_time - self.start_time
|
||||
|
||||
# Access parameters via virtual members
|
||||
var current_duration = self.duration
|
||||
var current_loop = self.loop
|
||||
|
||||
# Check if animation has completed its duration
|
||||
if self.duration > 0 && elapsed >= self.duration
|
||||
if self.loop
|
||||
if current_duration > 0 && elapsed >= current_duration
|
||||
if current_loop
|
||||
# Reset start time to create a looping effect
|
||||
# We calculate the precise new start time to avoid drift
|
||||
var loops_completed = elapsed / self.duration
|
||||
self.start_time = self.start_time + (loops_completed * self.duration)
|
||||
var loops_completed = elapsed / current_duration
|
||||
self.start_time = self.start_time + (loops_completed * current_duration)
|
||||
else
|
||||
# Animation completed, stop it
|
||||
self.stop()
|
||||
# Animation completed, make it inactive
|
||||
# Set directly in values map to avoid triggering on_param_changed
|
||||
self.values["is_running"] = false
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -106,55 +127,75 @@ class Animation : animation.pattern
|
||||
return true
|
||||
end
|
||||
|
||||
# Get the normalized progress of the animation (0 to 255)
|
||||
#
|
||||
# @return int - Progress from 0 (start) to 255 (end)
|
||||
def get_progress()
|
||||
if self.duration <= 0
|
||||
return 0 # Infinite animations always return 0 progress
|
||||
end
|
||||
|
||||
var elapsed = self.current_time - self.start_time
|
||||
var progress = elapsed % self.duration # Handle looping
|
||||
|
||||
# For non-looping animations, if we've reached exactly the duration,
|
||||
# return maximum progress instead of 0 (which would be the modulo result)
|
||||
if !self.loop && elapsed >= self.duration
|
||||
return 255
|
||||
end
|
||||
|
||||
return tasmota.scale_uint(progress, 0, self.duration, 0, 255)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Set the animation duration
|
||||
#
|
||||
# @param duration: int - New duration in milliseconds
|
||||
# @return self for method chaining
|
||||
def set_duration(duration)
|
||||
self.set_param("duration", duration)
|
||||
return self
|
||||
end
|
||||
|
||||
# Set whether the animation should loop
|
||||
#
|
||||
# @param loop: bool - Whether to loop the animation
|
||||
# @return self for method chaining
|
||||
def set_loop(loop)
|
||||
self.set_param("loop", int(loop))
|
||||
return self
|
||||
end
|
||||
|
||||
# Render the animation to the provided frame buffer
|
||||
# Animations can override this, but they inherit the base render method from Pattern
|
||||
# Default implementation renders a solid color (makes Animation equivalent to solid pattern)
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
# Call parent Pattern render method
|
||||
return super(self).render(frame, time_ms)
|
||||
# Access is_running via virtual member
|
||||
var current_is_running = self.is_running
|
||||
if !current_is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
self.update(time_ms)
|
||||
|
||||
# Access parameters via virtual members (auto-resolves ValueProviders)
|
||||
var current_color = self.color
|
||||
var current_opacity = self.opacity
|
||||
|
||||
# Fill the entire frame with the current color
|
||||
frame.fill_pixels(current_color)
|
||||
|
||||
# Apply resolved opacity if not full
|
||||
if current_opacity < 255
|
||||
frame.apply_brightness(current_opacity)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Get a color for a specific pixel position and time
|
||||
# Default implementation returns the animation's color (solid color for all pixels)
|
||||
#
|
||||
# @param pixel: int - Pixel index (0-based)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def get_color_at(pixel, time_ms)
|
||||
return self.get_param_value("color", time_ms)
|
||||
end
|
||||
|
||||
# Get a color based on time (convenience method)
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def get_color(time_ms)
|
||||
return self.get_color_at(0, time_ms)
|
||||
end
|
||||
|
||||
# Get the normalized progress of the animation (0 to 255)
|
||||
#
|
||||
# @return int - Progress from 0 (start) to 255 (end)
|
||||
def get_progress()
|
||||
var current_duration = self.duration
|
||||
if current_duration <= 0
|
||||
return 0 # Infinite animations always return 0 progress
|
||||
end
|
||||
|
||||
var elapsed = self.current_time - self.start_time
|
||||
var progress = elapsed % current_duration # Handle looping
|
||||
|
||||
# For non-looping animations, if we've reached exactly the duration,
|
||||
# return maximum progress instead of 0 (which would be the modulo result)
|
||||
var current_loop = self.loop
|
||||
if !current_loop && elapsed >= current_duration
|
||||
return 255
|
||||
end
|
||||
|
||||
return tasmota.scale_uint(progress, 0, current_duration, 0, 255)
|
||||
end
|
||||
|
||||
# String representation of the animation
|
||||
|
||||
@ -16,6 +16,7 @@ class AnimationEngine
|
||||
# State management
|
||||
var is_running # Whether engine is active
|
||||
var last_update # Last update time in milliseconds
|
||||
var time_ms # Current time in milliseconds (updated each frame)
|
||||
var fast_loop_closure # Stored closure for fast_loop registration
|
||||
|
||||
# Performance optimization
|
||||
@ -39,6 +40,7 @@ class AnimationEngine
|
||||
# Initialize state
|
||||
self.is_running = false
|
||||
self.last_update = 0
|
||||
self.time_ms = 0
|
||||
self.fast_loop_closure = nil
|
||||
self.render_needed = false
|
||||
end
|
||||
@ -166,6 +168,9 @@ class AnimationEngine
|
||||
current_time = tasmota.millis()
|
||||
end
|
||||
|
||||
# Update engine time
|
||||
self.time_ms = current_time
|
||||
|
||||
# Throttle updates to ~5ms intervals
|
||||
var delta_time = current_time - self.last_update
|
||||
if delta_time < 5
|
||||
@ -350,6 +355,10 @@ class AnimationEngine
|
||||
return self.strip
|
||||
end
|
||||
|
||||
def get_strip_length()
|
||||
return self.width
|
||||
end
|
||||
|
||||
def is_active()
|
||||
return self.is_running
|
||||
end
|
||||
|
||||
401
lib/libesp32/berry_animation/src/core/parameterized_object.be
Normal file
401
lib/libesp32/berry_animation/src/core/parameterized_object.be
Normal file
@ -0,0 +1,401 @@
|
||||
# ParameterizedObject - Base class for parameter management
|
||||
#
|
||||
# This class provides a common parameter management system that can be shared
|
||||
# between Animation and ValueProvider classes. It handles parameter validation,
|
||||
# storage, and retrieval with support for ValueProvider instances.
|
||||
#
|
||||
# Parameters are stored in a 'values' map and accessed via virtual instance variables
|
||||
# through member() and setmember() methods. Subclasses should not declare instance
|
||||
# variables for parameters, but use the PARAMS system only.
|
||||
|
||||
class ParameterizedObject
|
||||
var values # Map storing all parameter values
|
||||
var engine # Reference to the animation engine
|
||||
|
||||
# Static parameter definitions - should be overridden by subclasses
|
||||
static var PARAMS = {}
|
||||
|
||||
# Initialize parameter system
|
||||
#
|
||||
# @param engine: AnimationEngine - Reference to the animation engine (required)
|
||||
def init(engine)
|
||||
if engine == nil || type(engine) != "instance"
|
||||
raise "value_error", "ParameterizedObject requires an engine parameter"
|
||||
end
|
||||
|
||||
self.engine = engine
|
||||
self.values = {}
|
||||
self._init_parameter_values()
|
||||
end
|
||||
|
||||
# Private method to initialize parameter values from the class hierarchy
|
||||
def _init_parameter_values()
|
||||
import introspect
|
||||
|
||||
# Walk up the class hierarchy to initialize parameters with defaults
|
||||
var current_class = classof(self)
|
||||
while current_class != nil
|
||||
# Check if this class has PARAMS
|
||||
if introspect.contains(current_class, "PARAMS")
|
||||
var class_params = current_class.PARAMS
|
||||
# Initialize parameters from this class with their default values
|
||||
for param_name : class_params.keys()
|
||||
# Only set if not already set (child class defaults take precedence)
|
||||
if !self.values.contains(param_name)
|
||||
var param_def = class_params[param_name]
|
||||
if param_def.contains("default")
|
||||
self.values[param_name] = param_def["default"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Move to parent class
|
||||
current_class = super(current_class)
|
||||
end
|
||||
end
|
||||
|
||||
# Private method to check if a parameter exists in the class hierarchy
|
||||
#
|
||||
# @param name: string - Parameter name to check
|
||||
# @return bool - True if parameter exists in any class in the hierarchy
|
||||
def _has_param(name)
|
||||
import introspect
|
||||
|
||||
# Walk up the class hierarchy to find the parameter
|
||||
var current_class = classof(self)
|
||||
while current_class != nil
|
||||
# Check if this class has PARAMS
|
||||
if introspect.contains(current_class, "PARAMS")
|
||||
var class_params = current_class.PARAMS
|
||||
if class_params.contains(name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
# Move to parent class
|
||||
current_class = super(current_class)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
# Private method to get parameter definition from the class hierarchy
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @return map - Parameter definition or nil if not found
|
||||
def _get_param_def(name)
|
||||
import introspect
|
||||
|
||||
# Walk up the class hierarchy to find the parameter definition
|
||||
var current_class = classof(self)
|
||||
while current_class != nil
|
||||
# Check if this class has PARAMS
|
||||
if introspect.contains(current_class, "PARAMS")
|
||||
var class_params = current_class.PARAMS
|
||||
if class_params.contains(name)
|
||||
return class_params[name]
|
||||
end
|
||||
end
|
||||
|
||||
# Move to parent class
|
||||
current_class = super(current_class)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
# Virtual member access - allows obj.param_name syntax
|
||||
# This is called when accessing a member that doesn't exist as a real instance variable
|
||||
#
|
||||
# @param name: string - Parameter name being accessed
|
||||
# @return any - Resolved parameter value (ValueProvider resolved to actual value)
|
||||
def member(name)
|
||||
# Check if it's a parameter (either set in values or defined in PARAMS)
|
||||
if self.values.contains(name) || self._has_param(name)
|
||||
return self._resolve_parameter_value(name, self.engine.time_ms)
|
||||
end
|
||||
|
||||
# Not a parameter, raise attribute error (consistent with setmember behavior)
|
||||
raise "attribute_error", f"'{classname(self)}' object has no attribute '{name}'"
|
||||
end
|
||||
|
||||
# Virtual member assignment - allows obj.param_name = value syntax
|
||||
# This is called when setting a member that doesn't exist as a real instance variable
|
||||
#
|
||||
# @param name: string - Parameter name being set
|
||||
# @param value: any - Value to set (can be static value or ValueProvider)
|
||||
def setmember(name, value)
|
||||
# Check if it's a parameter in the class hierarchy and set it with validation
|
||||
if self._has_param(name)
|
||||
self._set_parameter_value(name, value)
|
||||
else
|
||||
# Not a parameter, this will cause an error in normal Berry behavior
|
||||
raise "attribute_error", f"'{classname(self)}' object has no attribute '{name}'"
|
||||
end
|
||||
end
|
||||
|
||||
# Internal method to set a parameter value with validation
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to set (can be static value or ValueProvider)
|
||||
def _set_parameter_value(name, value)
|
||||
# Validate the value (skip validation for ValueProvider instances)
|
||||
if !animation.is_value_provider(value)
|
||||
self._validate_param(name, value) # This will raise exception with details if invalid
|
||||
end
|
||||
|
||||
# Store the value
|
||||
self.values[name] = value
|
||||
|
||||
# Notify of parameter change
|
||||
self.on_param_changed(name, value)
|
||||
end
|
||||
|
||||
# Internal method to resolve a parameter value (handles ValueProviders)
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param time_ms: int - Current time in milliseconds for ValueProvider resolution
|
||||
# @return any - Resolved value (static or from ValueProvider)
|
||||
def _resolve_parameter_value(name, time_ms)
|
||||
if !self.values.contains(name)
|
||||
# Return default if available from class hierarchy
|
||||
var param_def = self._get_param_def(name)
|
||||
if param_def != nil && param_def.contains("default")
|
||||
return param_def["default"]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
var value = self.values[name]
|
||||
|
||||
# If it's a ValueProvider, resolve it using produce_value
|
||||
if animation.is_value_provider(value)
|
||||
return value.produce_value(name, time_ms)
|
||||
else
|
||||
# It's a static value, return as-is
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
# Validate a parameter value against its constraints
|
||||
# Raises detailed exceptions for validation failures
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to validate
|
||||
def _validate_param(name, value)
|
||||
var constraints = self._get_param_def(name)
|
||||
if constraints == nil
|
||||
raise "value_error", f"Parameter '{name}' is not defined for class '{classname(self)}'"
|
||||
end
|
||||
|
||||
# Accept ValueProvider instances for all parameters
|
||||
if animation.is_value_provider(value)
|
||||
return
|
||||
end
|
||||
|
||||
# Handle nil values
|
||||
if value == nil
|
||||
# Check if nil is explicitly allowed via nillable attribute
|
||||
if constraints.contains("nillable") && constraints["nillable"] == true
|
||||
return # nil is allowed for this parameter
|
||||
end
|
||||
|
||||
# Check if there's a default value (nil is acceptable if there's a default)
|
||||
if constraints.contains("default")
|
||||
return # nil is acceptable, will use default
|
||||
end
|
||||
|
||||
# nil is not allowed for this parameter
|
||||
raise "value_error", f"Parameter '{name}' does not accept nil values"
|
||||
end
|
||||
|
||||
# Type validation - default type is "int" if not specified
|
||||
var expected_type = "int" # Default type
|
||||
if constraints.contains("type")
|
||||
expected_type = constraints["type"]
|
||||
end
|
||||
|
||||
# Get actual type for validation
|
||||
var actual_type = type(value)
|
||||
|
||||
# Skip type validation if expected type is "any"
|
||||
if expected_type != "any"
|
||||
# Validate type
|
||||
if expected_type != actual_type
|
||||
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
|
||||
end
|
||||
end
|
||||
|
||||
# Range validation for integer values only
|
||||
if actual_type == "int"
|
||||
if constraints.contains("min") && value < constraints["min"]
|
||||
raise "value_error", f"Parameter '{name}' value {value} is below minimum {constraints['min']}"
|
||||
end
|
||||
if constraints.contains("max") && value > constraints["max"]
|
||||
raise "value_error", f"Parameter '{name}' value {value} is above maximum {constraints['max']}"
|
||||
end
|
||||
end
|
||||
|
||||
# Enum validation
|
||||
if constraints.contains("enum")
|
||||
var valid = false
|
||||
import introspect
|
||||
var enum_list = constraints["enum"]
|
||||
var list_size = enum_list.size()
|
||||
var i = 0
|
||||
while (i < list_size)
|
||||
var enum_value = enum_list[i]
|
||||
if value == enum_value
|
||||
valid = true
|
||||
break
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
if !valid
|
||||
raise "value_error", f"Parameter '{name}' value {value} is not in allowed values {enum_list}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Set a parameter value with validation
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to set
|
||||
# @return bool - True if parameter was set, false if validation failed
|
||||
def set_param(name, value)
|
||||
# Check if parameter exists in class hierarchy
|
||||
if !self._has_param(name)
|
||||
return false
|
||||
end
|
||||
|
||||
try
|
||||
self._set_parameter_value(name, value)
|
||||
return true
|
||||
except "value_error" as e
|
||||
# Validation failed - return false for method-based setting
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Get a parameter value (returns raw stored value, not resolved)
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param default_value: any - Default value if parameter not found
|
||||
# @return any - Parameter value or default (may be ValueProvider)
|
||||
def get_param(name, default_value)
|
||||
# Check stored values
|
||||
if self.values.contains(name)
|
||||
return self.values[name]
|
||||
end
|
||||
|
||||
# Fall back to parameter default from class hierarchy
|
||||
var param_def = self._get_param_def(name)
|
||||
if param_def != nil && param_def.contains("default")
|
||||
return param_def["default"]
|
||||
end
|
||||
|
||||
return default_value
|
||||
end
|
||||
|
||||
# Helper method to resolve a value that can be either static or from a value provider
|
||||
#
|
||||
# @param value: any - Static value or value provider instance
|
||||
# @param param_name: string - Parameter name for specific produce_value() method lookup
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def resolve_value(value, param_name, time_ms)
|
||||
if value == nil
|
||||
return nil
|
||||
end
|
||||
|
||||
if animation.is_value_provider(value)
|
||||
return value.produce_value(param_name, time_ms)
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
# Get parameter metadata
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @return map - Parameter metadata or nil if not found
|
||||
def get_param_metadata(name)
|
||||
return self._get_param_def(name)
|
||||
end
|
||||
|
||||
# Get all parameter metadata from class hierarchy
|
||||
#
|
||||
# @return map - Map of all parameter metadata
|
||||
def get_params_metadata()
|
||||
import introspect
|
||||
var all_params = {}
|
||||
|
||||
# Walk up the class hierarchy to collect all parameter definitions
|
||||
var current_class = classof(self)
|
||||
while current_class != nil
|
||||
# Check if this class has PARAMS
|
||||
if introspect.contains(current_class, "PARAMS")
|
||||
var class_params = current_class.PARAMS
|
||||
# Add parameters from this class (child class parameters override parent)
|
||||
for param_name : class_params.keys()
|
||||
if !all_params.contains(param_name) # Don't override child class params
|
||||
all_params[param_name] = class_params[param_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Move to parent class
|
||||
current_class = super(current_class)
|
||||
end
|
||||
|
||||
return all_params
|
||||
end
|
||||
|
||||
# Helper method to get a resolved value from either a static value or a value provider
|
||||
# This is the same as accessing obj.param_name but with explicit time
|
||||
#
|
||||
# @param param_name: string - Name of the parameter
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def get_param_value(param_name, time_ms)
|
||||
return self._resolve_parameter_value(param_name, time_ms)
|
||||
end
|
||||
|
||||
# Start the object - placeholder for future implementation
|
||||
#
|
||||
# @return self for method chaining
|
||||
def start(time_ms)
|
||||
return self
|
||||
end
|
||||
|
||||
# Method called when a parameter is changed
|
||||
# Subclasses should override this to handle parameter changes
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - New parameter value
|
||||
def on_param_changed(name, value)
|
||||
# Default implementation does nothing
|
||||
end
|
||||
|
||||
# Equality operator for object identity comparison
|
||||
# This prevents the member() method from being called during == comparisons
|
||||
#
|
||||
# @param other: any - Object to compare with
|
||||
# @return bool - True if objects are the same instance
|
||||
def ==(other)
|
||||
import introspect
|
||||
return introspect.toptr(self) == introspect.toptr(other)
|
||||
end
|
||||
|
||||
# Inequality operator for object identity comparison
|
||||
# This prevents the member() method from being called during != comparisons
|
||||
#
|
||||
# @param other: any - Object to compare with
|
||||
# @return bool - True if objects are different instances
|
||||
def !=(other)
|
||||
return !(self == other)
|
||||
end
|
||||
end
|
||||
|
||||
return {'parameterized_object': ParameterizedObject}
|
||||
@ -1,381 +0,0 @@
|
||||
# Pattern base class - The root of the animation hierarchy
|
||||
#
|
||||
# A Pattern defines WHAT should be displayed - it can generate colors for any pixel
|
||||
# at any time. Patterns have priority for layering and can be rendered directly.
|
||||
#
|
||||
# This is the base class for both simple patterns and complex animations.
|
||||
|
||||
#@ solidify:Pattern,weak
|
||||
class Pattern
|
||||
# Core pattern properties
|
||||
var priority # Rendering priority (higher = on top) (int)
|
||||
var opacity # Pattern opacity (0-255) (int)
|
||||
var name # Optional name for the pattern (string)
|
||||
var is_running # Whether the pattern is active (bool)
|
||||
var params # Map of pattern parameters with their constraints (map)
|
||||
var param_values # Map of current parameter values (map)
|
||||
|
||||
# Initialize a new pattern
|
||||
#
|
||||
# @param priority: int - Rendering priority (higher = on top), defaults to 10 if nil
|
||||
# @param opacity: int - Pattern opacity (0-255), defaults to 255 if nil
|
||||
# @param name: string - Optional name for the pattern, defaults to "pattern" if nil
|
||||
def init(priority, opacity, name)
|
||||
self.priority = priority != nil ? priority : 10
|
||||
self.opacity = opacity != nil ? opacity : 255
|
||||
self.name = name != nil ? name : "pattern"
|
||||
self.is_running = false
|
||||
self.params = {}
|
||||
self.param_values = {}
|
||||
|
||||
# Register common parameters with validation
|
||||
self._register_param("priority", {"min": 0, "default": 10})
|
||||
self._register_param("opacity", {"min": 0, "max": 255, "default": 255})
|
||||
|
||||
# Set initial values for common parameters
|
||||
self.set_param("priority", self.priority)
|
||||
self.set_param("opacity", self.opacity)
|
||||
end
|
||||
|
||||
# Start the pattern (make it active)
|
||||
#
|
||||
# @return self for method chaining
|
||||
def start()
|
||||
self.is_running = true
|
||||
return self
|
||||
end
|
||||
|
||||
# Stop the pattern (make it inactive)
|
||||
#
|
||||
# @return self for method chaining
|
||||
def stop()
|
||||
self.is_running = false
|
||||
return self
|
||||
end
|
||||
|
||||
# Update pattern state based on current time
|
||||
# Base patterns are typically stateless, but this allows for time-varying patterns
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if pattern is still active, false if completed
|
||||
def update(time_ms)
|
||||
return self.is_running
|
||||
end
|
||||
|
||||
# Render the pattern to the provided frame buffer
|
||||
# This is an abstract method that must be implemented by subclasses
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
# This is an abstract method that should be overridden by subclasses
|
||||
# The base implementation does nothing
|
||||
return false
|
||||
end
|
||||
|
||||
# Get a color for a specific pixel position and time
|
||||
# This is the core method that defines what a pattern looks like
|
||||
#
|
||||
# @param pixel: int - Pixel index (0-based)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def get_color_at(pixel, time_ms)
|
||||
# Base implementation returns white
|
||||
# Subclasses should override this to provide actual pattern logic
|
||||
return 0xFFFFFFFF
|
||||
end
|
||||
|
||||
# Set the pattern priority
|
||||
#
|
||||
# @param priority: int - New priority value
|
||||
# @return self for method chaining
|
||||
def set_priority(priority)
|
||||
self.set_param("priority", priority)
|
||||
return self
|
||||
end
|
||||
|
||||
# Set the pattern opacity
|
||||
#
|
||||
# @param opacity: int - New opacity value (0-255)
|
||||
# @return self for method chaining
|
||||
def set_opacity(opacity)
|
||||
self.set_param("opacity", opacity)
|
||||
return self
|
||||
end
|
||||
|
||||
# Register a parameter with validation constraints
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param constraints: map - Validation constraints for the parameter
|
||||
# @return self for method chaining
|
||||
def _register_param(name, constraints)
|
||||
if constraints == nil
|
||||
constraints = {}
|
||||
end
|
||||
|
||||
self.params[name] = constraints
|
||||
return self
|
||||
end
|
||||
|
||||
# Register a new parameter with validation constraints
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param constraints: map - Validation constraints for the parameter
|
||||
# @return self for method chaining
|
||||
def register_param(name, constraints)
|
||||
return self._register_param(name, constraints)
|
||||
end
|
||||
|
||||
# Validate a parameter value against its constraints
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to validate
|
||||
# @return bool - True if valid, false otherwise
|
||||
def _validate_param(name, value)
|
||||
if !self.params.contains(name)
|
||||
return false # Parameter not registered
|
||||
end
|
||||
|
||||
var constraints = self.params[name]
|
||||
|
||||
# Check if value is nil and there's a default
|
||||
if value == nil && constraints.contains("default")
|
||||
value = constraints["default"]
|
||||
end
|
||||
|
||||
# Accept ValueProvider instances for all parameters
|
||||
if animation.is_value_provider(value)
|
||||
return true
|
||||
end
|
||||
|
||||
# Only accept integer values
|
||||
if type(value) != "int"
|
||||
return false
|
||||
end
|
||||
|
||||
# Range validation for integer values
|
||||
if constraints.contains("min") && value < constraints["min"]
|
||||
return false
|
||||
end
|
||||
if constraints.contains("max") && value > constraints["max"]
|
||||
return false
|
||||
end
|
||||
|
||||
# Enum validation
|
||||
if constraints.contains("enum")
|
||||
var valid = false
|
||||
import introspect
|
||||
var enum_list = constraints["enum"]
|
||||
var list_size = enum_list.size()
|
||||
var i = 0
|
||||
while (i < list_size)
|
||||
var enum_value = enum_list[i]
|
||||
if value == enum_value
|
||||
valid = true
|
||||
break
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
if !valid
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Set a parameter value with validation
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - Value to set
|
||||
# @return bool - True if parameter was set, false if validation failed
|
||||
def set_param(name, value)
|
||||
import introspect
|
||||
|
||||
# Check if parameter exists
|
||||
if !self.params.contains(name)
|
||||
return false
|
||||
end
|
||||
|
||||
# Validate the value
|
||||
if !animation.is_value_provider(value)
|
||||
if !self._validate_param(name, value)
|
||||
return false
|
||||
end
|
||||
|
||||
if introspect.contains(self, name)
|
||||
self.(name) = value
|
||||
else
|
||||
self.param_values[name] = value
|
||||
end
|
||||
|
||||
self.on_param_changed(name, value)
|
||||
else
|
||||
if introspect.contains(self, name)
|
||||
self.(name) = value
|
||||
else
|
||||
self.param_values[name] = value
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Get a parameter value
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param default_value: any - Default value if parameter not found
|
||||
# @return any - Parameter value or default
|
||||
def get_param(name, default_value)
|
||||
import introspect
|
||||
var method_name = "get_" + name
|
||||
if introspect.contains(self, method_name)
|
||||
var method = self.(method_name)
|
||||
return method(self) # since it's not a method call, we need to pass the self as first parameter
|
||||
end
|
||||
|
||||
if self.param_values.contains(name)
|
||||
return self.param_values[name]
|
||||
end
|
||||
|
||||
if introspect.contains(self, name)
|
||||
return self.(name)
|
||||
end
|
||||
|
||||
if self.params.contains(name) && self.params[name].contains("default")
|
||||
return self.params[name]["default"]
|
||||
end
|
||||
|
||||
return default_value
|
||||
end
|
||||
|
||||
# Helper method to resolve a value that can be either static or from a value provider
|
||||
#
|
||||
# @param value: any - Static value or value provider instance
|
||||
# @param param_name: string - Parameter name for specific get_XXX() method lookup
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def resolve_value(value, param_name, time_ms)
|
||||
if value == nil
|
||||
return nil
|
||||
end
|
||||
|
||||
if animation.is_value_provider(value)
|
||||
if animation.is_color_provider(value)
|
||||
return value.get_color(time_ms)
|
||||
end
|
||||
|
||||
var method_name = "get_" + param_name
|
||||
import introspect
|
||||
var method = introspect.get(value, method_name)
|
||||
|
||||
if type(method) == "function"
|
||||
return method(value, time_ms)
|
||||
else
|
||||
return value.get_value(time_ms)
|
||||
end
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
# Get parameter metadata
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @return map - Parameter metadata or nil if not found
|
||||
def get_param_metadata(name)
|
||||
if self.params.contains(name)
|
||||
return self.params[name]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# Get all parameter metadata
|
||||
#
|
||||
# @return map - Map of all parameter metadata
|
||||
def get_params_metadata()
|
||||
return self.params
|
||||
end
|
||||
|
||||
# Get all parameter values
|
||||
#
|
||||
# @return map - Map of all parameter values
|
||||
def get_params()
|
||||
return self.param_values
|
||||
end
|
||||
|
||||
# Helper method to get a value from either a static value or a value provider
|
||||
# This method checks if the parameter contains a value provider instance,
|
||||
# and if so, calls the appropriate get_XXX() method on it.
|
||||
#
|
||||
# @param param_name: string - Name of the parameter
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - The resolved value (static or from provider)
|
||||
def get_param_value(param_name, time_ms)
|
||||
var param_value = self.get_param(param_name, nil)
|
||||
|
||||
if param_value == nil
|
||||
return nil
|
||||
end
|
||||
|
||||
# Check if it's a value provider instance
|
||||
if animation.is_value_provider(param_value)
|
||||
# Check for ColorProvider first for optimal color handling
|
||||
if animation.is_color_provider(param_value)
|
||||
return param_value.get_color(time_ms)
|
||||
end
|
||||
|
||||
# Try to call the specific get_XXX method for this parameter
|
||||
var method_name = "get_" + param_name
|
||||
|
||||
# Use introspect to check if the method exists
|
||||
import introspect
|
||||
var method = introspect.get(param_value, method_name)
|
||||
|
||||
if type(method) == "function"
|
||||
# Call the specific method (e.g., get_pulse_size())
|
||||
return method(param_value, time_ms) # Pass the instance as first argument (self)
|
||||
else
|
||||
# Fall back to generic get_value method
|
||||
return param_value.get_value(time_ms)
|
||||
end
|
||||
else
|
||||
# It's a static value, return as-is
|
||||
return param_value
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method to set a parameter that can be either a static value or a value provider
|
||||
# This method automatically wraps static values in a StaticValueProvider if needed
|
||||
#
|
||||
# @param param_name: string - Name of the parameter
|
||||
# @param value: any - Static value or value provider instance
|
||||
# @return bool - True if parameter was set successfully
|
||||
def set_param_value(param_name, value)
|
||||
# If it's already a value provider, use it directly
|
||||
if animation.is_value_provider(value)
|
||||
return self.set_param(param_name, value)
|
||||
else
|
||||
# It's a static value, wrap it in a StaticValueProvider
|
||||
var static_provider = animation.static_value_provider(value)
|
||||
return self.set_param(param_name, static_provider)
|
||||
end
|
||||
end
|
||||
|
||||
# Method called when a parameter is changed
|
||||
# Subclasses should override this to handle parameter changes
|
||||
#
|
||||
# @param name: string - Parameter name
|
||||
# @param value: any - New parameter value
|
||||
def on_param_changed(name, value)
|
||||
# Base implementation does nothing
|
||||
end
|
||||
|
||||
# String representation of the pattern
|
||||
def tostring()
|
||||
return f"Pattern({self.name}, priority={self.priority}, opacity={self.opacity}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
return {'pattern': Pattern}
|
||||
@ -26,7 +26,7 @@ class SequenceManager
|
||||
|
||||
self.steps = steps
|
||||
self.step_index = 0
|
||||
self.step_start_time = tasmota.millis()
|
||||
self.step_start_time = self.controller.time_ms
|
||||
self.is_running = true
|
||||
|
||||
if size(self.steps) > 0
|
||||
@ -48,7 +48,7 @@ class SequenceManager
|
||||
return
|
||||
end
|
||||
|
||||
var current_time = tasmota.millis()
|
||||
var current_time = self.controller.time_ms
|
||||
var current_step = self.steps[self.step_index]
|
||||
|
||||
# Check if current step has completed
|
||||
@ -80,7 +80,7 @@ class SequenceManager
|
||||
|
||||
# Set duration if specified
|
||||
if step.contains("duration") && step["duration"] > 0
|
||||
anim.set_duration(step["duration"])
|
||||
anim.duration = step["duration"]
|
||||
end
|
||||
|
||||
elif step["type"] == "wait"
|
||||
@ -89,11 +89,10 @@ class SequenceManager
|
||||
|
||||
elif step["type"] == "stop"
|
||||
var anim = step["animation"]
|
||||
anim.stop()
|
||||
self.controller.remove_animation(anim)
|
||||
end
|
||||
|
||||
self.step_start_time = tasmota.millis()
|
||||
self.step_start_time = self.controller.time_ms
|
||||
end
|
||||
|
||||
# Advance to the next step in the sequence
|
||||
@ -102,7 +101,6 @@ class SequenceManager
|
||||
var current_step = self.steps[self.step_index]
|
||||
if current_step["type"] == "play" && current_step.contains("duration")
|
||||
var anim = current_step["animation"]
|
||||
anim.stop()
|
||||
self.controller.remove_animation(anim)
|
||||
end
|
||||
|
||||
@ -131,7 +129,7 @@ class SequenceManager
|
||||
"step_index": self.step_index,
|
||||
"total_steps": size(self.steps),
|
||||
"current_step": self.steps[self.step_index],
|
||||
"elapsed_ms": tasmota.millis() - self.step_start_time
|
||||
"elapsed_ms": self.controller.time_ms - self.step_start_time
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,750 +0,0 @@
|
||||
# API Reference
|
||||
|
||||
Complete reference for the Tasmota Berry Animation Framework API.
|
||||
|
||||
## Core Classes
|
||||
|
||||
### AnimationEngine
|
||||
|
||||
The central controller for all animations.
|
||||
|
||||
```berry
|
||||
var engine = animation.create_engine(strip)
|
||||
```
|
||||
|
||||
#### Methods
|
||||
|
||||
**`add_animation(animation)`**
|
||||
- Adds an animation to the engine
|
||||
- Auto-starts the animation if engine is running
|
||||
- Returns: `self` (for method chaining)
|
||||
|
||||
**`remove_animation(animation)`**
|
||||
- Removes an animation from the engine
|
||||
- Returns: `self`
|
||||
|
||||
**`clear()`**
|
||||
- Removes all animations
|
||||
- Returns: `self`
|
||||
|
||||
**`start()`**
|
||||
- Starts the engine and all animations
|
||||
- Integrates with Tasmota's `fast_loop`
|
||||
- Returns: `self`
|
||||
|
||||
**`stop()`**
|
||||
- Stops the engine and all animations
|
||||
- Returns: `self`
|
||||
|
||||
**`size()`**
|
||||
- Returns: Number of active animations
|
||||
|
||||
**`is_active()`**
|
||||
- Returns: `true` if engine is running
|
||||
|
||||
#### Example
|
||||
```berry
|
||||
var strip = Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
var pulse = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
|
||||
engine.add_animation(pulse).start()
|
||||
```
|
||||
|
||||
### Pattern (Base Class)
|
||||
|
||||
Base class for all visual elements.
|
||||
|
||||
#### Properties
|
||||
- **`priority`** (int) - Rendering priority (higher = on top)
|
||||
- **`opacity`** (int) - Opacity 0-255 for blending
|
||||
- **`name`** (string) - Pattern identification
|
||||
- **`is_running`** (bool) - Whether pattern is active
|
||||
|
||||
#### Methods
|
||||
|
||||
**`start()`** / **`stop()`**
|
||||
- Control pattern lifecycle
|
||||
- Returns: `self`
|
||||
|
||||
**`set_priority(priority)`**
|
||||
- Set rendering priority
|
||||
- Returns: `self`
|
||||
|
||||
**`set_opacity(opacity)`**
|
||||
- Set opacity (0-255)
|
||||
- Returns: `self`
|
||||
|
||||
### Animation (Extends Pattern)
|
||||
|
||||
Adds temporal behavior to patterns.
|
||||
|
||||
#### Additional Properties
|
||||
- **`duration`** (int) - Animation duration in ms (0 = infinite)
|
||||
- **`loop`** (bool) - Whether to loop when complete
|
||||
- **`start_time`** (int) - When animation started
|
||||
- **`current_time`** (int) - Current animation time
|
||||
|
||||
#### Additional Methods
|
||||
|
||||
**`set_duration(duration_ms)`**
|
||||
- Set animation duration
|
||||
- Returns: `self`
|
||||
|
||||
**`set_loop(loop)`**
|
||||
- Enable/disable looping
|
||||
- Returns: `self`
|
||||
|
||||
**`get_progress()`**
|
||||
- Returns: Animation progress (0-255)
|
||||
|
||||
## Animation Functions
|
||||
|
||||
### Basic Animations
|
||||
|
||||
**`animation.solid(color, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates solid color animation
|
||||
- **color**: ARGB color value (0xAARRGGBB) or ValueProvider instance
|
||||
- Returns: `PatternAnimation` instance
|
||||
|
||||
```berry
|
||||
var red = animation.solid(0xFFFF0000)
|
||||
var blue = animation.solid(0xFF0000FF, 10, 5000, true, 200, "blue_anim")
|
||||
var dynamic = animation.solid(animation.smooth(0xFF000000, 0xFFFFFFFF, 3000))
|
||||
```
|
||||
|
||||
**`animation.pulse(pattern, period_ms, min_brightness=0, max_brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates pulsing animation
|
||||
- **pattern**: Base pattern to pulse
|
||||
- **period_ms**: Pulse period in milliseconds
|
||||
- **min_brightness**: Minimum brightness (0-255)
|
||||
- **max_brightness**: Maximum brightness (0-255)
|
||||
- Returns: `PulseAnimation` instance
|
||||
|
||||
```berry
|
||||
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
```
|
||||
|
||||
**`animation.breathe(color, period_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates smooth breathing effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **period_ms**: Breathing period in milliseconds
|
||||
- Returns: `BreatheAnimation` instance
|
||||
|
||||
```berry
|
||||
var breathe_blue = animation.breathe(0xFF0000FF, 4000)
|
||||
var dynamic_breathe = animation.breathe(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00], 2000), 4000)
|
||||
```
|
||||
|
||||
### Palette-Based Animations
|
||||
|
||||
**`animation.rich_palette_animation(palette, period_ms, transition_type=1, brightness=255, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates palette-based color cycling animation
|
||||
- **palette**: Palette in VRGB bytes format or palette name
|
||||
- **period_ms**: Cycle period in milliseconds
|
||||
- **transition_type**: 0=linear, 1=smooth (sine)
|
||||
- **brightness**: Overall brightness (0-255)
|
||||
- Returns: `FilledAnimation` instance
|
||||
|
||||
```berry
|
||||
var rainbow = animation.rich_palette_animation(animation.PALETTE_RAINBOW, 5000, 1, 255)
|
||||
```
|
||||
|
||||
### Position-Based Animations
|
||||
|
||||
**`animation.pulse_position_animation(color, pos, pulse_size, slew_size=0, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates pulse at specific position
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **pos**: Pixel position (0-based) or ValueProvider instance
|
||||
- **pulse_size**: Width of pulse in pixels or ValueProvider instance
|
||||
- **slew_size**: Fade region size in pixels or ValueProvider instance
|
||||
- Returns: `PulsePositionAnimation` instance
|
||||
|
||||
```berry
|
||||
var center_pulse = animation.pulse_position_animation(0xFFFFFFFF, 15, 3, 2)
|
||||
var moving_pulse = animation.pulse_position_animation(0xFFFF0000, animation.smooth(0, 29, 3000), 3, 2)
|
||||
```
|
||||
|
||||
**`animation.comet_animation(color, tail_length, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates moving comet effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **tail_length**: Length of comet tail in pixels
|
||||
- **speed_ms**: Movement speed in milliseconds per pixel
|
||||
- Returns: `CometAnimation` instance
|
||||
|
||||
```berry
|
||||
var comet = animation.comet_animation(0xFF00FFFF, 8, 100)
|
||||
var rainbow_comet = animation.comet_animation(animation.rich_palette_color_provider(animation.PALETTE_RAINBOW, 3000), 8, 100)
|
||||
```
|
||||
|
||||
**`animation.twinkle_animation(color, density, speed_ms, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates twinkling stars effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- **density**: Number of twinkling pixels
|
||||
- **speed_ms**: Twinkle speed in milliseconds
|
||||
- Returns: `TwinkleAnimation` instance
|
||||
|
||||
```berry
|
||||
var stars = animation.twinkle_animation(0xFFFFFFFF, 5, 500)
|
||||
var color_changing_stars = animation.twinkle_animation(animation.color_cycle_color_provider([0xFFFF0000, 0xFF00FF00, 0xFF0000FF], 4000), 5, 500)
|
||||
```
|
||||
|
||||
### Fire and Natural Effects
|
||||
|
||||
**`animation.fire_animation(color=nil, intensity=200, speed_ms=100, priority=0, duration=0, loop=false, opacity=255, name="")`**
|
||||
- Creates realistic fire simulation
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for default fire palette
|
||||
- **intensity**: Fire intensity (0-255)
|
||||
- **speed_ms**: Animation speed in milliseconds
|
||||
- Returns: `FireAnimation` instance
|
||||
|
||||
```berry
|
||||
var fire = animation.fire_animation(nil, 180, 150) # Default fire palette
|
||||
var blue_fire = animation.fire_animation(0xFF0066FF, 180, 150) # Blue fire
|
||||
```
|
||||
|
||||
### Advanced Pattern Animations
|
||||
|
||||
**`animation.noise_rainbow(scale, speed, strip_length, priority)`**
|
||||
- Creates rainbow noise pattern with fractal complexity
|
||||
- **scale**: Noise frequency/detail (0-255, higher = more detail)
|
||||
- **speed**: Animation speed (0-255, 0 = static)
|
||||
- **strip_length**: LED strip length
|
||||
- **priority**: Rendering priority
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
**`animation.noise_single_color(color, scale, speed, strip_length, priority)`**
|
||||
- Creates single-color noise pattern
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
**`animation.noise_fractal(color, scale, speed, octaves, strip_length, priority)`**
|
||||
- Creates multi-octave fractal noise
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
|
||||
- **octaves**: Number of noise octaves (1-4)
|
||||
- Returns: `NoiseAnimation` instance
|
||||
|
||||
```berry
|
||||
var rainbow_noise = animation.noise_rainbow(60, 40, 30, 10)
|
||||
var blue_noise = animation.noise_single_color(0xFF0066FF, 120, 60, 30, 10)
|
||||
var fractal = animation.noise_fractal(nil, 40, 50, 3, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.plasma_rainbow(time_speed, strip_length, priority)`**
|
||||
- Creates rainbow plasma effect using sine wave interference
|
||||
- **time_speed**: Animation speed (0-255)
|
||||
- Returns: `PlasmaAnimation` instance
|
||||
|
||||
**`animation.plasma_single_color(color, time_speed, strip_length, priority)`**
|
||||
- Creates single-color plasma effect
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `PlasmaAnimation` instance
|
||||
|
||||
```berry
|
||||
var plasma = animation.plasma_rainbow(80, 30, 10)
|
||||
var purple_plasma = animation.plasma_single_color(0xFF8800FF, 60, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.sparkle_white(density, fade_speed, strip_length, priority)`**
|
||||
- Creates white twinkling sparkles
|
||||
- **density**: Sparkle creation probability (0-255)
|
||||
- **fade_speed**: Fade-out speed (0-255)
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
**`animation.sparkle_colored(color, density, fade_speed, strip_length, priority)`**
|
||||
- Creates colored sparkles
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
**`animation.sparkle_rainbow(density, fade_speed, strip_length, priority)`**
|
||||
- Creates rainbow sparkles
|
||||
- Returns: `SparkleAnimation` instance
|
||||
|
||||
```berry
|
||||
var white_sparkles = animation.sparkle_white(80, 60, 30, 10)
|
||||
var red_sparkles = animation.sparkle_colored(0xFFFF0000, 100, 50, 30, 10)
|
||||
var rainbow_sparkles = animation.sparkle_rainbow(60, 40, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.wave_rainbow_sine(amplitude, wave_speed, strip_length, priority)`**
|
||||
- Creates rainbow sine wave pattern
|
||||
- **amplitude**: Wave amplitude/intensity (0-255)
|
||||
- **wave_speed**: Wave movement speed (0-255)
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
**`animation.wave_single_sine(color, amplitude, wave_speed, strip_length, priority)`**
|
||||
- Creates single-color sine wave
|
||||
- **color**: ARGB color value or ValueProvider instance
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
**`animation.wave_custom(color, wave_type, amplitude, frequency, strip_length, priority)`**
|
||||
- Creates custom wave with specified type
|
||||
- **color**: ARGB color value, ValueProvider instance, or nil for rainbow
|
||||
- **wave_type**: 0=sine, 1=triangle, 2=square, 3=sawtooth
|
||||
- **frequency**: Wave frequency/density (0-255)
|
||||
- Returns: `WaveAnimation` instance
|
||||
|
||||
```berry
|
||||
var sine_wave = animation.wave_rainbow_sine(40, 80, 30, 10)
|
||||
var green_wave = animation.wave_single_sine(0xFF00FF00, 60, 40, 30, 10)
|
||||
var triangle_wave = animation.wave_custom(nil, 1, 50, 70, 30, 10)
|
||||
```
|
||||
|
||||
### Motion Effect Animations
|
||||
|
||||
Motion effects transform existing animations by applying movement, scaling, and distortion effects.
|
||||
|
||||
**`animation.shift_scroll_right(source, speed, strip_length, priority)`**
|
||||
- Scrolls animation to the right with wrapping
|
||||
- **source**: Source animation to transform
|
||||
- **speed**: Scroll speed (0-255)
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
**`animation.shift_scroll_left(source, speed, strip_length, priority)`**
|
||||
- Scrolls animation to the left with wrapping
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
**`animation.shift_bounce_horizontal(source, speed, strip_length, priority)`**
|
||||
- Bounces animation horizontally at strip edges
|
||||
- Returns: `ShiftAnimation` instance
|
||||
|
||||
```berry
|
||||
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
|
||||
var scrolling = animation.shift_scroll_right(base, 100, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.bounce_gravity(source, speed, gravity, strip_length, priority)`**
|
||||
- Physics-based bouncing with gravity simulation
|
||||
- **source**: Source animation to transform
|
||||
- **speed**: Initial bounce speed (0-255)
|
||||
- **gravity**: Gravity strength (0-255)
|
||||
- Returns: `BounceAnimation` instance
|
||||
|
||||
**`animation.bounce_basic(source, speed, damping, strip_length, priority)`**
|
||||
- Basic bouncing without gravity
|
||||
- **damping**: Damping factor (0-255, 255=no damping)
|
||||
- Returns: `BounceAnimation` instance
|
||||
|
||||
```berry
|
||||
var sparkles = animation.sparkle_white(80, 50, 30, 5)
|
||||
var bouncing = animation.bounce_gravity(sparkles, 150, 80, 30, 10)
|
||||
var elastic = animation.bounce_basic(sparkles, 120, 240, 30, 10)
|
||||
```
|
||||
|
||||
**`animation.scale_static(source, scale_factor, strip_length, priority)`**
|
||||
- Static scaling of animation
|
||||
- **source**: Source animation to transform
|
||||
- **scale_factor**: Scale factor (128=1.0x, 64=0.5x, 255=2.0x)
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
**`animation.scale_oscillate(source, speed, strip_length, priority)`**
|
||||
- Oscillating scale (breathing effect)
|
||||
- **speed**: Oscillation speed (0-255)
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
**`animation.scale_grow(source, speed, strip_length, priority)`**
|
||||
- Growing scale effect
|
||||
- Returns: `ScaleAnimation` instance
|
||||
|
||||
```berry
|
||||
var pattern = animation.gradient_rainbow_linear(0, 30, 5)
|
||||
var breathing = animation.scale_oscillate(pattern, 60, 30, 10)
|
||||
var zoomed = animation.scale_static(pattern, 180, 30, 10) # 1.4x scale
|
||||
```
|
||||
|
||||
**`animation.jitter_position(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random position shake effects
|
||||
- **source**: Source animation to transform
|
||||
- **intensity**: Jitter intensity (0-255)
|
||||
- **frequency**: Jitter frequency (0-255, maps to 0-30 Hz)
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_color(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random color variations
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_brightness(source, intensity, frequency, strip_length, priority)`**
|
||||
- Random brightness changes
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
**`animation.jitter_all(source, intensity, frequency, strip_length, priority)`**
|
||||
- Combination of position, color, and brightness jitter
|
||||
- Returns: `JitterAnimation` instance
|
||||
|
||||
```berry
|
||||
var base = animation.gradient_rainbow_linear(0, 30, 5)
|
||||
var glitch = animation.jitter_all(base, 120, 100, 30, 15)
|
||||
var shake = animation.jitter_position(base, 60, 40, 30, 10)
|
||||
```
|
||||
|
||||
### Chaining Motion Effects
|
||||
|
||||
Motion effects can be chained together for complex transformations:
|
||||
|
||||
```berry
|
||||
# Base animation
|
||||
var base = animation.pulse_animation(0xFF0066FF, 80, 180, 3000, 5, 0, true, "base")
|
||||
|
||||
# Apply multiple transformations
|
||||
var scaled = animation.scale_static(base, 150, 30, 8) # 1.2x scale
|
||||
var shifted = animation.shift_scroll_left(scaled, 60, 30, 12) # Scroll left
|
||||
var jittered = animation.jitter_color(shifted, 40, 30, 30, 15) # Add color jitter
|
||||
|
||||
# Result: A scaled, scrolling, color-jittered pulse
|
||||
```
|
||||
|
||||
## Color System
|
||||
|
||||
### Color Formats
|
||||
|
||||
**ARGB Format**: `0xAARRGGBB`
|
||||
- **AA**: Alpha channel (opacity) - usually `FF` for opaque
|
||||
- **RR**: Red component (00-FF)
|
||||
- **GG**: Green component (00-FF)
|
||||
- **BB**: Blue component (00-FF)
|
||||
|
||||
```berry
|
||||
var red = 0xFFFF0000 # Opaque red
|
||||
var semi_blue = 0x800000FF # Semi-transparent blue
|
||||
var white = 0xFFFFFFFF # Opaque white
|
||||
var black = 0xFF000000 # Opaque black
|
||||
```
|
||||
|
||||
### Predefined Colors
|
||||
|
||||
```berry
|
||||
# Available as constants
|
||||
animation.COLOR_RED # 0xFFFF0000
|
||||
animation.COLOR_GREEN # 0xFF00FF00
|
||||
animation.COLOR_BLUE # 0xFF0000FF
|
||||
animation.COLOR_WHITE # 0xFFFFFFFF
|
||||
animation.COLOR_BLACK # 0xFF000000
|
||||
```
|
||||
|
||||
### Palette System
|
||||
|
||||
**Creating Palettes**
|
||||
```berry
|
||||
# VRGB format: Value(position), Red, Green, Blue
|
||||
var fire_palette = bytes("00000000" "80FF0000" "FFFFFF00")
|
||||
# ^pos=0 ^pos=128 ^pos=255
|
||||
# black red yellow
|
||||
```
|
||||
|
||||
**Predefined Palettes**
|
||||
```berry
|
||||
animation.PALETTE_RAINBOW # Standard rainbow colors
|
||||
animation.PALETTE_FIRE # Fire effect colors
|
||||
animation.PALETTE_OCEAN # Ocean wave colors
|
||||
```
|
||||
|
||||
## Value Providers
|
||||
|
||||
Dynamic parameters that change over time.
|
||||
|
||||
### Static Values
|
||||
```berry
|
||||
# Regular values are automatically wrapped
|
||||
var static_color = 0xFFFF0000
|
||||
var static_position = 15
|
||||
```
|
||||
|
||||
### Oscillator Providers
|
||||
|
||||
**`animation.smooth(start, end, period_ms)`**
|
||||
- Smooth cosine wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.linear(start, end, period_ms)`**
|
||||
- Triangle wave oscillation (goes from start to end, then back to start)
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.triangle(start, end, period_ms)`**
|
||||
- Alias for `linear()` - triangle wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.ramp(start, end, period_ms)`**
|
||||
- Sawtooth wave oscillation (linear progression from start to end)
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.sawtooth(start, end, period_ms)`**
|
||||
- Alias for `ramp()` - sawtooth wave oscillation
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
**`animation.square(start, end, period_ms, duty_cycle=50)`**
|
||||
- Square wave oscillation
|
||||
- **duty_cycle**: Percentage of time at high value
|
||||
- Returns: `OscillatorValueProvider`
|
||||
|
||||
```berry
|
||||
# Dynamic position that moves back and forth
|
||||
var moving_pos = animation.smooth(0, 29, 3000)
|
||||
|
||||
# Dynamic color that cycles brightness
|
||||
var breathing_color = animation.smooth(50, 255, 2000)
|
||||
|
||||
# Use with animations
|
||||
var dynamic_pulse = animation.pulse_position_animation(
|
||||
0xFFFF0000, # Static red color
|
||||
moving_pos, # Dynamic position
|
||||
3, # Static pulse size
|
||||
1 # Static slew size
|
||||
)
|
||||
```
|
||||
|
||||
## Event System
|
||||
|
||||
### Event Registration
|
||||
|
||||
**`animation.register_event_handler(event_name, callback, priority=0, condition=nil, metadata=nil)`**
|
||||
- Registers an event handler
|
||||
- **event_name**: Name of event to handle
|
||||
- **callback**: Function to call when event occurs
|
||||
- **priority**: Handler priority (higher = executed first)
|
||||
- **condition**: Optional condition function
|
||||
- **metadata**: Optional metadata map
|
||||
- Returns: `EventHandler` instance
|
||||
|
||||
```berry
|
||||
def flash_white(event_data)
|
||||
var flash = animation.solid(0xFFFFFFFF)
|
||||
engine.add_animation(flash)
|
||||
end
|
||||
|
||||
var handler = animation.register_event_handler("button_press", flash_white, 10)
|
||||
```
|
||||
|
||||
### Event Triggering
|
||||
|
||||
**`animation.trigger_event(event_name, event_data={})`**
|
||||
- Triggers an event
|
||||
- **event_name**: Name of event to trigger
|
||||
- **event_data**: Data to pass to handlers
|
||||
|
||||
```berry
|
||||
animation.trigger_event("button_press", {"button": "main"})
|
||||
```
|
||||
|
||||
## DSL System
|
||||
|
||||
### DSL Runtime
|
||||
|
||||
**`animation.DSLRuntime(engine, debug_mode=false)`**
|
||||
- Creates DSL runtime instance
|
||||
- **engine**: AnimationEngine instance
|
||||
- **debug_mode**: Enable debug output
|
||||
- Returns: `DSLRuntime` instance
|
||||
|
||||
#### Methods
|
||||
|
||||
**`load_dsl(source_code)`**
|
||||
- Compiles and executes DSL source code
|
||||
- **source_code**: DSL source as string
|
||||
- Returns: `true` on success, `false` on error
|
||||
|
||||
**`load_dsl_file(filename)`**
|
||||
- Loads and executes DSL from file
|
||||
- **filename**: Path to .anim file
|
||||
- Returns: `true` on success, `false` on error
|
||||
|
||||
```berry
|
||||
var runtime = animation.DSLRuntime(engine, true) # Debug mode on
|
||||
|
||||
var dsl_code = '''
|
||||
color red = #FF0000
|
||||
animation pulse_red = pulse(solid(red), 2s, 50%, 100%)
|
||||
run pulse_red
|
||||
'''
|
||||
|
||||
if runtime.load_dsl(dsl_code)
|
||||
print("Animation loaded successfully")
|
||||
else
|
||||
print("Failed to load animation")
|
||||
end
|
||||
```
|
||||
|
||||
### DSL Compilation
|
||||
|
||||
**`animation.compile_dsl(source_code)`**
|
||||
- Compiles DSL to Berry code
|
||||
- **source_code**: DSL source as string
|
||||
- Returns: Berry code string or raises exception
|
||||
- Raises: `"dsl_compilation_error"` on compilation failure
|
||||
|
||||
```berry
|
||||
try
|
||||
var berry_code = animation.compile_dsl(dsl_source)
|
||||
print("Generated code:", berry_code)
|
||||
var compiled_func = compile(berry_code)
|
||||
compiled_func()
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("Compilation error:", msg)
|
||||
end
|
||||
```
|
||||
|
||||
## User Functions
|
||||
|
||||
### Function Registration
|
||||
|
||||
**`animation.register_user_function(name, func)`**
|
||||
- Registers Berry function for DSL use
|
||||
- **name**: Function name for DSL
|
||||
- **func**: Berry function to register
|
||||
|
||||
**`animation.is_user_function(name)`**
|
||||
- Checks if function is registered
|
||||
- Returns: `true` if registered
|
||||
|
||||
**`animation.get_user_function(name)`**
|
||||
- Gets registered function
|
||||
- Returns: Function or `nil`
|
||||
|
||||
**`animation.list_user_functions()`**
|
||||
- Lists all registered function names
|
||||
- Returns: Array of function names
|
||||
|
||||
```berry
|
||||
def custom_breathing(color, period)
|
||||
return animation.pulse(animation.solid(color), period, 50, 255)
|
||||
end
|
||||
|
||||
animation.register_user_function("breathing", custom_breathing)
|
||||
|
||||
# Now available in DSL:
|
||||
# animation my_effect = breathing(red, 3s)
|
||||
```
|
||||
|
||||
## Version Information
|
||||
|
||||
The framework uses a numeric version system for efficient comparison:
|
||||
|
||||
```berry
|
||||
# Primary version (0xAABBCCDD format: AA=major, BB=minor, CC=patch, DD=build)
|
||||
print(f"0x{animation.VERSION:08X}") # 0x00010000
|
||||
|
||||
# Convert to string format (drops build number)
|
||||
print(animation.version_string()) # "0.1.0"
|
||||
|
||||
# Convert any version number to string
|
||||
print(animation.version_string(0x01020304)) # "1.2.3"
|
||||
|
||||
# Extract components manually
|
||||
var major = (animation.VERSION >> 24) & 0xFF # 0
|
||||
var minor = (animation.VERSION >> 16) & 0xFF # 1
|
||||
var patch = (animation.VERSION >> 8) & 0xFF # 0
|
||||
var build = animation.VERSION & 0xFF # 0
|
||||
|
||||
# Version comparison
|
||||
var is_new_enough = animation.VERSION >= 0x00010000 # v0.1.0+
|
||||
```
|
||||
|
||||
## Utility Functions
|
||||
|
||||
### Global Variable Access
|
||||
|
||||
**`animation.global(name)`**
|
||||
- Safely accesses global variables
|
||||
- **name**: Variable name
|
||||
- Returns: Variable value
|
||||
- Raises: `"syntax_error"` if variable doesn't exist
|
||||
|
||||
```berry
|
||||
# Set global variable
|
||||
global.my_color = 0xFFFF0000
|
||||
|
||||
# Access safely
|
||||
var color = animation.global("my_color") # Returns 0xFFFF0000
|
||||
var missing = animation.global("missing") # Raises exception
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
**`animation.is_value_provider(obj)`**
|
||||
- Checks if object is a ValueProvider
|
||||
- Returns: `true` if object implements ValueProvider interface
|
||||
|
||||
**`animation.is_color_provider(obj)`**
|
||||
- Checks if object is a ColorProvider
|
||||
- Returns: `true` if object implements ColorProvider interface
|
||||
|
||||
```berry
|
||||
var static_val = 42
|
||||
var dynamic_val = animation.smooth(0, 100, 2000)
|
||||
|
||||
print(animation.is_value_provider(static_val)) # false
|
||||
print(animation.is_value_provider(dynamic_val)) # true
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Exceptions
|
||||
|
||||
- **`"dsl_compilation_error"`** - DSL compilation failed
|
||||
- **`"syntax_error"`** - Variable not found or syntax error
|
||||
- **`"type_error"`** - Invalid parameter type
|
||||
- **`"runtime_error"`** - General runtime error
|
||||
|
||||
### Best Practices
|
||||
|
||||
```berry
|
||||
# Always use try/catch for DSL operations
|
||||
try
|
||||
runtime.load_dsl(dsl_code)
|
||||
except "dsl_compilation_error" as e, msg
|
||||
print("DSL Error:", msg)
|
||||
except .. as e, msg
|
||||
print("Unexpected error:", msg)
|
||||
end
|
||||
|
||||
# Check engine state before operations
|
||||
if engine.is_active()
|
||||
engine.add_animation(new_animation)
|
||||
else
|
||||
print("Engine not running")
|
||||
end
|
||||
|
||||
# Validate parameters
|
||||
if type(color) == "int" && color >= 0
|
||||
var anim = animation.solid(color)
|
||||
else
|
||||
print("Invalid color value")
|
||||
end
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### Memory Management
|
||||
```berry
|
||||
# Clear animations when switching effects
|
||||
engine.clear()
|
||||
engine.add_animation(new_animation)
|
||||
|
||||
# Reuse animation objects when possible
|
||||
var pulse_red = animation.pulse(animation.solid(0xFFFF0000), 2000, 50, 255)
|
||||
# Use pulse_red multiple times instead of creating new instances
|
||||
```
|
||||
|
||||
### Timing Optimization
|
||||
```berry
|
||||
# Use longer periods for smoother performance
|
||||
var smooth_pulse = animation.pulse(pattern, 3000, 50, 255) # 3 seconds
|
||||
var choppy_pulse = animation.pulse(pattern, 100, 50, 255) # 100ms - may be choppy
|
||||
|
||||
# Limit simultaneous animations
|
||||
# Good: 1-3 animations
|
||||
# Avoid: 10+ animations running simultaneously
|
||||
```
|
||||
|
||||
### Value Provider Efficiency
|
||||
```berry
|
||||
# Efficient: Reuse providers
|
||||
var breathing = animation.smooth(50, 255, 2000)
|
||||
var anim1 = animation.pulse(pattern1, breathing)
|
||||
var anim2 = animation.pulse(pattern2, breathing) # Reuse same provider
|
||||
|
||||
# Inefficient: Create new providers
|
||||
var anim1 = animation.pulse(pattern1, animation.smooth(50, 255, 2000))
|
||||
var anim2 = animation.pulse(pattern2, animation.smooth(50, 255, 2000)) # Duplicate
|
||||
```
|
||||
|
||||
This API reference covers the essential classes and functions. For more advanced usage, see the [Examples](EXAMPLES.md) and [User Functions](.kiro/specs/berry-animation-framework/USER_FUNCTIONS.md) documentation.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user