diff --git a/lib/libesp32/berry_animation/README.md b/lib/libesp32/berry_animation/README.md index a4bc9abe5..aa591ad3c 100644 --- a/lib/libesp32/berry_animation/README.md +++ b/lib/libesp32/berry_animation/README.md @@ -22,7 +22,7 @@ The DSL **transpiles to standard Berry code**, so you get the best of both world Test and create animations without a Tasmota device using the online emulator: -[![Tasmota_Berry_LED_emulator](berry_animation_docs/emulator_screenshot.png){width=353}](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank} +[![Tasmota_Berry_LED_emulator](animation_docs/emulator_screenshot.png){width=353}](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank} **[https://tasmota.github.io/docs/Tasmota-Berry-emulator/](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html){target=_blank}** @@ -172,10 +172,10 @@ Animation|Description ## Documentation -- **[Quick Start Guide](berry_animation_docs/QUICK_START.md)** - Get running in 5 minutes -- **[DSL Reference](berry_animation_docs/DSL_REFERENCE.md)** - Complete syntax reference -- **[Examples](berry_animation_docs/EXAMPLES.md)** - Comprehensive examples -- **[Animation Classes](berry_animation_docs/ANIMATION_CLASS_HIERARCHY.md)** - All animations and parameters -- **[Oscillation Patterns](berry_animation_docs/OSCILLATION_PATTERNS.md)** - Dynamic value waveforms -- **[User Functions](berry_animation_docs/USER_FUNCTIONS.md)** - Extend with custom Berry functions -- **[Troubleshooting](berry_animation_docs/TROUBLESHOOTING.md)** - Common issues and solutions +- **[Quick Start Guide](animation_docs/Quick_Start.md)** - Get running in 5 minutes +- **[DSL Reference](animation_docs/Dsl_Reference.md)** - Complete syntax reference +- **[Examples](animation_docs/Examples.md)** - Comprehensive examples +- **[Animation Classes](animation_docs/Animation_Class_Hierarchy.md)** - All animations and parameters +- **[Oscillation Patterns](animation_docs/Oscillation_Patterns.md)** - Dynamic value waveforms +- **[User Functions](animation_docs/User_Functions.md)** - Extend with custom Berry functions +- **[Troubleshooting](animation_docs/Troubleshooting.md)** - Common issues and solutions diff --git a/lib/libesp32/berry_animation/anim_tutorials/chap_7_20_crenel_nb_pulses.anim b/lib/libesp32/berry_animation/anim_tutorials/chap_7_20_crenel_nb_pulses.anim index 5efa6ed72..7fbffced7 100644 --- a/lib/libesp32/berry_animation/anim_tutorials/chap_7_20_crenel_nb_pulses.anim +++ b/lib/libesp32/berry_animation/anim_tutorials/chap_7_20_crenel_nb_pulses.anim @@ -1,9 +1,11 @@ -# @desc Crenel static with variable number of pulses +# Crenel with variable number of pulses -# Move from 0 to 5 and back -set nb_pulse = triangle(min_value = 0, max_value = 5, duration = 2s) +set strip_len = strip_length() +set period = 4 # pulse_size (2) + low_size (2) +set max_pulses = (strip_len + period - 1) / period + +set nb_pulse = triangle(min_value = 0, max_value = max_pulses, duration = 2s) -# Define a simple crenel 2+2 red/blue animation back = crenel_animation( color = red back_color = blue diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_10.png deleted file mode 100644 index eda18aa7e..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_20.png deleted file mode 100644 index 15c5fd165..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_30.png deleted file mode 100644 index b5f28b529..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_40.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_40.png deleted file mode 100644 index c78685474..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_40.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_50.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_50.png deleted file mode 100644 index 3aa6f4270..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_1_50.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_10.png deleted file mode 100644 index 1368e2a5f..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_20.png deleted file mode 100644 index d5e84a1e4..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_2_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_10.png deleted file mode 100644 index 65a36b4c3..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_20.png deleted file mode 100644 index d7e0bcbe1..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_3_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_10.png deleted file mode 100644 index fd1d95a62..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_12.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_12.png deleted file mode 100644 index 435739cce..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_12.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_15.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_15.png deleted file mode 100644 index 4120e1809..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_15.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_18.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_18.png deleted file mode 100644 index 27949d8e3..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_18.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_30.png deleted file mode 100644 index 903a10e0e..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_35.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_35.png deleted file mode 100644 index 1bc9511e2..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_4_35.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_10.png deleted file mode 100644 index 9f9cbc6a2..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_15.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_15.png deleted file mode 100644 index 04067afd3..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_15.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_20.png deleted file mode 100644 index 705c538ff..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_30.png deleted file mode 100644 index d4f412c7d..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_40.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_40.png deleted file mode 100644 index 3543e92b5..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_40.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_50.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_50.png deleted file mode 100644 index 9ef0bc5e9..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_5_50.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_10.png deleted file mode 100644 index 99e33ecea..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_20.png deleted file mode 100644 index 24bea18ef..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_30.png deleted file mode 100644 index 8149a277b..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_40.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_40.png deleted file mode 100644 index b833db5f9..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_6_40.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_10.png deleted file mode 100644 index 043fe2fc3..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_20.png deleted file mode 100644 index 31a5eb4ff..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_30.png deleted file mode 100644 index 37c3084d5..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_40.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_40.png deleted file mode 100644 index a1dae722a..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_40.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_50.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_50.png deleted file mode 100644 index c5581fb9d..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_7_50.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_10.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_10.png deleted file mode 100644 index d45876812..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_10.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_20.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_20.png deleted file mode 100644 index d06b78def..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_20.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_30.png b/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_30.png deleted file mode 100644 index e3ef5bea0..000000000 Binary files a/lib/libesp32/berry_animation/anim_tutorials/png/chap_8_30.png and /dev/null differ diff --git a/lib/libesp32/berry_animation/animation_docs/Animation_Class_Hierarchy.md b/lib/libesp32/berry_animation/animation_docs/Animation_Class_Hierarchy.md new file mode 100644 index 000000000..ddb397ed1 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Animation_Class_Hierarchy.md @@ -0,0 +1,1197 @@ +# Berry Animation Framework - Class Hierarchy and Parameters Reference + +This document provides a comprehensive reference for all classes in the Berry Animation Framework that extend `ParameterizedObject`, including their parameters and factory functions. + +## Table of Contents + +1. [Class Hierarchy](#class-hierarchy) +2. [Base Classes](#base-classes) +3. [Value Providers](#value-providers) +4. [Color Providers](#color-providers) +5. [Animation Classes](#animation-classes) +6. [Parameter Constraints](#parameter-constraints) + +## Class Hierarchy + +``` +ParameterizedObject (base class with parameter management and playable interface) +├── Animation (unified base class for all visual elements) +│ ├── EngineProxy (combines rendering and orchestration) +│ │ └── (user-defined template animations) +│ ├── SolidAnimation (solid color fill) +│ ├── BeaconAnimation (pulse at specific position) +│ ├── CrenelPositionAnimation (crenel/square wave pattern) +│ ├── BreatheAnimation (breathing effect) +│ ├── PaletteGradientAnimation (gradient patterns with palette colors) +│ │ ├── PaletteMeterAnimation (meter/bar patterns) +│ │ └── GradientMeterAnimation (VU meter with gradient colors and peak hold) +│ ├── CometAnimation (moving comet with tail) +│ ├── FireAnimation (realistic fire effect) +│ ├── TwinkleAnimation (twinkling stars effect) +│ ├── GradientAnimation (color gradients) +│ ├── NoiseAnimation (Perlin noise patterns) +│ ├── WaveAnimation (wave motion effects) +│ └── RichPaletteAnimation (smooth palette transitions) +├── SequenceManager (orchestrates animation sequences) +└── ValueProvider (dynamic value generation) + ├── StaticValueProvider (wraps static values) + ├── StripLengthProvider (provides LED strip length) + ├── IterationNumberProvider (provides sequence iteration number) + ├── OscillatorValueProvider (oscillating values with waveforms) + ├── ClosureValueProvider (computed values, internal use only) + └── ColorProvider (dynamic color generation) + ├── StaticColorProvider (solid color) + ├── ColorCycleColorProvider (cycles through palette) + ├── RichPaletteColorProvider (smooth palette transitions) + ├── BreatheColorProvider (breathing color effect) + └── CompositeColorProvider (blends multiple colors) +``` + +## Base Classes + +### ParameterizedObject + +Base class for all parameterized objects in the framework. Provides parameter management with validation, storage, and retrieval, as well as the playable interface for lifecycle management (start/stop/update). + +This unified base class enables: +- Consistent parameter handling across all framework objects +- Unified engine management (animations and sequences treated uniformly) +- Hybrid objects that combine rendering and orchestration +- Consistent lifecycle management (start/stop/update) + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `is_running` | bool | false | - | Whether the object is active | + +**Key Methods**: +- `start(time_ms)` - Start the object at a specific time +- `stop()` - Stop the object +- `update(time_ms)` - Update object state based on current time (no return value) + +**Factory**: N/A (base class) + +### Animation + +Unified base class for all visual elements. Inherits from `ParameterizedObject`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `id` | string | "animation" | - | Optional name for the animation | +| `is_running` | bool | false | - | Whether the animation is active | +| `priority` | int | 10 | 0-255 | Rendering priority (higher = on top) | +| `duration` | int | 0 | min: 0 | Animation duration in ms (0 = infinite) | +| `loop` | bool | false | - | Whether to loop when duration is reached | +| `opacity` | any | 255 | - | Animation opacity (number, FrameBuffer, or Animation) | +| `color` | int | 0xFFFFFFFF | - | Base color in ARGB format | + +**Special Behavior**: Setting `is_running = true/false` starts/stops the animation. + +**Timing Behavior**: The `start()` method only resets the time origin if the animation was already started previously (i.e., `self.start_time` is not nil). The first actual rendering tick occurs in `update()` or `render()` methods, which initialize `start_time` on first call. + +**Factory**: `animation.animation(engine)` + +### EngineProxy + +A specialized animation class that combines rendering and orchestration capabilities. Extends `Animation` and can contain child animations and sequences. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| *(inherits all Animation parameters)* | | | | | + +**Key Features**: +- Can render visual content like a regular animation +- Can orchestrate sub-animations and sequences using `add()` +- Enables complex composite effects +- Used as base class for template animations + +**Child Management**: +- `add(obj)` - Adds a child animation or sequence +- `remove(obj)` - Removes a child +- Children are automatically started/stopped with parent +- Children are rendered in priority order (higher priority on top) + +**Use Cases**: +- Composite effects combining multiple animations +- Template animations with parameters +- Complex patterns with timing control +- Reusable animation components + +**Factory**: `animation.engine_proxy(engine)` + +### Template Animations + +Template animations are user-defined classes that extend `EngineProxy`, created using the DSL's `template animation` syntax. They provide reusable, parameterized animation patterns. + +**DSL Definition**: +```berry +template animation shutter_effect { + param colors type palette nillable true + param duration type time min 0 max 3600 default 5 nillable false + + # Animation definition with sequences, colors, etc. + # Parameters accessed as self.colors, self.duration +} +``` + +**Generated Class Structure**: +```berry +class shutter_effect_animation : animation.engine_proxy + static var PARAMS = animation.enc_params({ + "colors": {"type": "palette", "nillable": true}, + "duration": {"type": "time", "min": 0, "max": 3600, "default": 5, "nillable": false} + }) + + def init(engine) + super(self).init(engine) + # Generated code with self.colors and self.duration references + # Uses self.add() for sub-animations and sequences + end +end +``` + +**Parameter Constraints**: +Template animation parameters support all standard constraints: +- `type` - Parameter type (palette, time, int, color, etc.) +- `min` - Minimum value (for numeric types) +- `max` - Maximum value (for numeric types) +- `default` - Default value +- `nillable` - Whether parameter can be nil (true/false) + +**Implicit Parameters**: +Template animations automatically inherit parameters from the `EngineProxy` class hierarchy without explicit declaration: +- `id` (string, default: "animation") - Animation name +- `priority` (int, default: 10) - Rendering priority +- `duration` (int, default: 0) - Animation duration in milliseconds +- `loop` (bool, default: false) - Whether animation loops +- `opacity` (int, default: 255) - Animation opacity (0-255) +- `color` (int, default: 0) - Base color value +- `is_running` (bool, default: false) - Running state + +These parameters can be used directly in template animation bodies without declaration: +```berry +template animation fade_effect { + param colors type palette + + # 'duration' is implicit - no need to declare + set oscillator = sawtooth(min_value=0, max_value=255, duration=duration) + + color col = color_cycle(colors=colors, period=0) + animation test = solid(color=col) + test.opacity = oscillator # 'opacity' is also implicit + + run test +} +``` + +**Usage**: +```berry +# Create instance with parameters +palette rainbow = [red, orange, yellow, green, blue] +animation my_shutter = shutter_effect(colors=rainbow, duration=2s) +run my_shutter +``` + +**Key Differences from Regular Animations**: +- Defined in DSL, not Berry code +- Parameters accessed as `self.` instead of direct variables +- Uses `self.add()` for composition +- Can be instantiated multiple times with different parameters +- Automatically registered as animation constructors + +**Factory**: User-defined (e.g., `shutter_effect(engine)`) + +## Value Providers + +Value providers generate dynamic values over time for use as animation parameters. + +### ValueProvider + +Base interface for all value providers. Inherits from `ParameterizedObject`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| *(none)* | - | - | - | Base interface has no parameters | + +**Timing Behavior**: For value providers, `start()` is typically not called because instances can be embedded in closures. Value providers consider the first call to `produce_value()` as the start of their internal time reference. The `start()` method only resets the time origin if the provider was already started previously (i.e., `self.start_time` is not nil). + +**Update Method**: The `update(time_ms)` method does not return any value. Subclasses should check `self.is_running` to determine if the object is still active. + +**Factory**: N/A (base interface) + +### StaticValueProvider + +Wraps static values to provide ValueProvider interface. Inherits from `ValueProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `value` | any | nil | - | The static value to return | + +**Factory**: `animation.static_value(engine)` + +### StripLengthProvider + +Provides access to the LED strip length as a dynamic value. Inherits from `ValueProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| *(none)* | - | - | - | No parameters - strip length obtained from engine | + +**Usage**: Returns the 1D length of the LED strip in pixels. Useful for animations that need to know the strip dimensions for positioning, scaling, or boundary calculations. + +**Factory**: `animation.strip_length(engine)` + +### OscillatorValueProvider + +Generates oscillating values using various waveforms. Inherits from `ValueProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `min_value` | int | 0 | - | Minimum oscillation value | +| `max_value` | int | 255 | - | Maximum oscillation value | +| `duration` | int | 1000 | min: 1 | Oscillation period in milliseconds | +| `form` | int | 1 | enum: [1,2,3,4,5,6,7,8,9] | Waveform type | +| `phase` | int | 0 | 0-255 | Phase shift in 0-255 range (mapped to duration) | +| `duty_cycle` | int | 127 | 0-255 | Duty cycle for square/triangle waves in 0-255 range | + +**Waveform Constants**: +- `1` (SAWTOOTH) - Linear ramp from min to max +- `2` (TRIANGLE) - Linear ramp from min to max and back +- `3` (SQUARE) - Square wave alternating between min and max +- `4` (COSINE) - Smooth cosine wave +- `5` (SINE) - Pure sine wave +- `6` (EASE_IN) - Quadratic acceleration +- `7` (EASE_OUT) - Quadratic deceleration +- `8` (ELASTIC) - Spring-like overshoot and oscillation +- `9` (BOUNCE) - Ball-like bouncing with decreasing amplitude + +**Timing Behavior**: The `start_time` is initialized on the first call to `produce_value()`. The `start()` method only resets the time origin if the oscillator was already started previously (i.e., `self.start_time` is not nil). + +**Factories**: `animation.ramp(engine)`, `animation.sawtooth(engine)`, `animation.linear(engine)`, `animation.triangle(engine)`, `animation.smooth(engine)`, `animation.sine_osc(engine)`, `animation.cosine_osc(engine)`, `animation.square(engine)`, `animation.ease_in(engine)`, `animation.ease_out(engine)`, `animation.elastic(engine)`, `animation.bounce(engine)`, `animation.oscillator_value(engine)` + +**See Also**: [Oscillation Patterns](Oscillation_Patterns.md) - Visual examples and usage patterns for oscillation waveforms + +### ClosureValueProvider + +**⚠️ INTERNAL USE ONLY - NOT FOR DIRECT USE** + +Wraps a closure/function as a value provider for internal transpiler use. This class is used internally by the DSL transpiler to handle computed values and should not be used directly by users. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `closure` | function | nil | - | The closure function to call for value generation | + +**Internal Usage**: This provider is automatically created by the DSL transpiler when it encounters computed expressions or arithmetic operations involving value providers. The closure is called with `(self, param_name, time_ms)` parameters. + +#### Mathematical Helper Methods + +The ClosureValueProvider includes built-in mathematical helper methods that can be used within closures for computed values: + +| Method | Description | Parameters | Return Type | Example | +|--------|-------------|------------|-------------|---------| +| `min(a, b, ...)` | Minimum of two or more values | `a, b, *args: number` | `number` | `animation._math.min(5, 3, 8)` → `3` | +| `max(a, b, ...)` | Maximum of two or more values | `a, b, *args: number` | `number` | `animation._math.max(5, 3, 8)` → `8` | +| `abs(x)` | Absolute value | `x: number` | `number` | `animation._math.abs(-5)` → `5` | +| `round(x)` | Round to nearest integer | `x: number` | `int` | `animation._math.round(3.7)` → `4` | +| `sqrt(x)` | Square root with integer handling | `x: number` | `number` | `animation._math.sqrt(64)` → `128` (for 0-255 range) | +| `scale(v, from_min, from_max, to_min, to_max)` | Scale value between ranges | `v, from_min, from_max, to_min, to_max: number` | `int` | `animation._math.scale(50, 0, 100, 0, 255)` → `127` | +| `sin(angle)` | Sine function (0-255 input range) | `angle: number` | `int` | `animation._math.sin(64)` → `255` (90°) | +| `cos(angle)` | Cosine function (0-255 input range) | `angle: number` | `int` | `animation._math.cos(0)` → `-255` (matches oscillator behavior) | + +**Mathematical Method Notes:** + +- **Integer Handling**: `sqrt()` treats integers in 0-255 range as normalized values (255 = 1.0) +- **Angle Range**: `sin()` and `cos()` use 0-255 input range (0-360 degrees) +- **Output Range**: Trigonometric functions return -255 to 255 (mapped from -1.0 to 1.0) +- **Cosine Behavior**: Matches oscillator COSINE waveform (starts at minimum, not maximum) +- **Scale Function**: Uses `tasmota.scale_int()` for efficient integer scaling + +#### Closure Signature + +Closures used with ClosureValueProvider must follow this signature: +```berry +def closure_func(engine, param_name, time_ms) + # engine: AnimationEngine reference + # param_name: Name of the parameter being computed + # time_ms: Current time in milliseconds + return computed_value +end +``` + +#### Usage in Computed Values + +These methods are automatically available in DSL computed expressions: + +```berry +# Example: Dynamic brightness based on strip position +set strip_len = strip_length() +animation pulse = pulsating_animation( + color=red + brightness=strip_len / 4 + 50 # Uses built-in arithmetic +) + +# Complex mathematical expressions are automatically wrapped in closures +# that have access to all mathematical helper methods +``` + +**Factory**: `animation.closure_value(engine)` (internal use only) + +**Note**: Users should not create ClosureValueProvider instances directly. Instead, use the DSL's computed value syntax which automatically creates these providers as needed. + +## Color Providers + +Color providers generate dynamic colors over time, extending ValueProvider for color-specific functionality. + +### ColorProvider + +Base interface for all color providers. Inherits from `ValueProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `brightness` | int | 255 | 0-255 | Overall brightness scaling for all colors | + +**Static Methods**: +- `apply_brightness(color, brightness)` - Applies brightness scaling to a color (ARGB format). Only performs scaling if brightness is not 255 (full brightness). This is a static utility method that can be called without an instance. + +**Factory**: N/A (base interface) + +### StaticColorProvider + +Returns a single, static color. Inherits from `ColorProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | The solid color to return | +| *(inherits brightness from ColorProvider)* | | | | | + +#### Usage Examples + +```berry +# Using predefined colors +color static_red = solid(color=red) +color static_blue = solid(color=blue) + +# Using hex colors +color static_orange = solid(color=0xFF8C00) + +# Using custom defined colors +color accent = 0xFF6B35 +color static_accent = solid(color=accent) +``` + +**Note**: The `solid()` function is the recommended shorthand for `static_color()`. + +### ColorCycleColorProvider + +Cycles through a palette of colors with brutal switching. Inherits from `ColorProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `colors` | bytes | default palette | - | Palette bytes in AARRGGBB format | +| `period` | int | 5000 | min: 0 | Cycle time in ms (0 = manual only) | +| `next` | int | 0 | - | Write 1 to move to next color manually, or any number to go forward or backwards by `n` colors | +| `palette_size` | int | 3 | read-only | Number of colors in the palette (automatically updated when palette changes) | +| *(inherits brightness from ColorProvider)* | | | | | + +**Note**: The `get_color_for_value()` method accepts values in the 0-255 range for value-based color mapping. + +**Modes**: Auto-cycle (`period > 0`) or Manual-only (`period = 0`) + +#### Usage Examples + +```berry +# RGB cycle with brutal switching +color rgb_cycle = color_cycle( + colors=bytes("FF0000FF" "FF00FF00" "FFFF0000"), + period=4s +) + +# Custom warm colors +color warm_cycle = color_cycle( + colors=bytes("FF4500FF" "FF8C00FF" "FFFF00"), + period=3s +) + +# Mixed colors in AARRGGBB format +color mixed_cycle = color_cycle( + colors=bytes("FFFF0000" "FF00FF00" "FF0000FF"), + period=2s +) +``` + +### RichPaletteColorProvider + +Generates colors from predefined palettes with smooth transitions and professional color schemes. Inherits from `ColorProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `colors` | bytes | rainbow palette | - | Palette bytes or predefined palette constant | +| `period` | int | 5000 | min: 0 | Cycle time in ms (0 = value-based only) | +| `transition_type` | int | animation.LINEAR | enum: [animation.LINEAR, animation.SINE] | LINEAR=constant speed, SINE=smooth ease-in/ease-out | +| *(inherits brightness from ColorProvider)* | | | | | + +#### Available Predefined Palettes + +| Palette | Description | Colors | +|---------|-------------|---------| +| `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 | + +#### Usage Examples + +```berry +# Rainbow palette with smooth ease-in/ease-out transitions +color rainbow_colors = rich_palette( + colors=PALETTE_RAINBOW, + period=5s, + transition_type=SINE, + brightness=255 +) + +# Fire effect with linear (constant speed) transitions +color fire_colors = rich_palette( + colors=PALETTE_FIRE, + period=3s, + transition_type=LINEAR, + brightness=200 +) +``` + +### BreatheColorProvider + +Creates breathing/pulsing color effects by modulating the brightness of a base color over time. Inherits from `ColorProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `base_color` | int | 0xFFFFFFFF | - | The base color to modulate (32-bit ARGB value) | +| `min_brightness` | int | 0 | 0-255 | Minimum brightness level (breathing effect) | +| `max_brightness` | int | 255 | 0-255 | Maximum brightness level (breathing effect) | +| `duration` | int | 3000 | min: 1 | Time for one complete breathing cycle in ms | +| `curve_factor` | int | 2 | 1-5 | Breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses) | +| *(inherits brightness from ColorProvider)* | | | | Overall brightness scaling applied after breathing effect | +| *(inherits all OscillatorValueProvider parameters)* | | | | | + +**Curve Factor Effects:** +- `1`: Pure cosine wave (smooth pulsing, equivalent to pulsating_color) +- `2`: Natural breathing with slight pauses at peaks +- `3`: More pronounced breathing with longer pauses +- `4`: Deep breathing with extended pauses +- `5`: Most pronounced pauses at peaks (dramatic breathing effect) + +#### Usage Examples + +```berry +# Natural breathing effect +color breathing_red = breathe_color( + base_color=red, + min_brightness=20, + max_brightness=255, + duration=4s, + curve_factor=3 +) + +# Fast pulsing effect (equivalent to pulsating_color) +color pulse_blue = breathe_color( + base_color=blue, + min_brightness=50, + max_brightness=200, + duration=1s, + curve_factor=1 +) + +# Slow, deep breathing +color deep_breath = breathe_color( + base_color=purple, + min_brightness=5, + max_brightness=255, + duration=6s, + curve_factor=4 +) + +# Using dynamic base color +color rainbow_cycle = color_cycle(colors=bytes("FF0000FF" "FF00FF00" "FFFF0000"), period=5s) +color breathing_rainbow = breathe_color( + base_color=rainbow_cycle, + min_brightness=30, + max_brightness=255, + duration=3s, + curve_factor=2 +) +``` + +**Factories**: `animation.breathe_color(engine)`, `animation.pulsating_color(engine)` + +**Note**: The `pulsating_color()` factory creates a BreatheColorProvider with `curve_factor=1` and `duration=1000ms` for fast pulsing effects. + +### CompositeColorProvider + +Combines multiple color providers with blending. Inherits from `ColorProvider`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `blend_mode` | int | 0 | enum: [0,1,2] | 0=overlay, 1=add, 2=multiply | +| *(inherits brightness from ColorProvider)* | | | | Overall brightness scaling applied to final composite color | + +**Factory**: `animation.composite_color(engine)` + +## Animation Classes + +All animation classes extend the base `Animation` class and inherit its parameters. + +### BreatheAnimation + +Creates a smooth breathing effect with natural breathing curves. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | The color to breathe | +| `min_brightness` | int | 0 | 0-255 | Minimum brightness level | +| `max_brightness` | int | 255 | 0-255 | Maximum brightness level | +| `period` | int | 3000 | min: 100 | Breathing cycle time in ms | +| `curve_factor` | int | 2 | 1-5 | Breathing curve shape (higher = sharper) | +| *(inherits all Animation parameters)* | | | | | + +**Factory**: `animation.breathe_animation(engine)` + +### CometAnimation + +Creates a comet effect with a bright head and fading tail. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | Color for the comet head | +| `tail_length` | int | 5 | 1-50 | Length of the comet tail in pixels | +| `speed` | int | 2560 | 1-25600 | Movement speed in 1/256th pixels per second | +| `direction` | int | 1 | enum: [-1,1] | Direction of movement (1=forward, -1=backward) | +| `wrap_around` | int | 1 | 0-1 | Whether comet wraps around the strip | +| `fade_factor` | int | 179 | 0-255 | How quickly the tail fades | +| *(inherits all Animation parameters)* | | | | | + +**Factory**: `animation.comet_animation(engine)` + + + + +### FireAnimation + +Creates a realistic fire effect with flickering flames. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | instance | nil | - | Color provider for fire palette (nil = default fire palette) | +| `intensity` | int | 180 | 0-255 | Overall fire intensity | +| `flicker_speed` | int | 8 | 1-20 | Flicker update frequency in Hz | +| `flicker_amount` | int | 100 | 0-255 | Amount of random flicker | +| `cooling_rate` | int | 55 | 0-255 | How quickly flames cool down | +| `sparking_rate` | int | 120 | 0-255 | Rate of new spark generation | +| *(inherits all Animation parameters)* | | | | | + +**Factory**: `animation.fire_animation(engine)` + +### GradientAnimation + +Creates smooth color gradients that can be linear or radial. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | instance | nil | nillable | Color provider (nil = rainbow gradient) | +| `gradient_type` | int | 0 | 0-1 | 0=linear, 1=radial | +| `direction` | int | 0 | 0-255 | Gradient direction/orientation | +| `center_pos` | int | 128 | 0-255 | Center position for radial gradients | +| `spread` | int | 255 | 1-255 | Gradient spread/compression | +| `movement_speed` | int | 0 | 0-255 | Speed of gradient movement | +| *(inherits all Animation parameters)* | | | | | + +**Factories**: `animation.gradient_animation(engine)`, `animation.gradient_rainbow_linear(engine)`, `animation.gradient_rainbow_radial(engine)`, `animation.gradient_two_color_linear(engine)` + +### GradientMeterAnimation + +VU meter style animation that displays a gradient-colored bar from the start of the strip up to a configurable level. Includes optional peak hold indicator. Inherits from `PaletteGradientAnimation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `level` | int | 255 | 0-255 | Current meter level (0=empty, 255=full) | +| `peak_hold` | int | 1000 | min: 0 | Peak hold time in ms (0=disabled) | +| *(inherits all PaletteGradientAnimation parameters)* | | | | | + +#### Visual Representation + +``` +level=128 (50%), peak at 200 +[████████████████--------•-------] +^ ^ +| peak indicator (single pixel) +filled gradient area +``` + +#### Usage Examples + +```berry +# Simple meter with rainbow gradient +color rainbow = rich_palette() +animation meter = gradient_meter_animation() +meter.color_source = rainbow +meter.level = 128 + +# Meter with peak hold (1 second) +color fire_colors = rich_palette(colors=PALETTE_FIRE) +animation vu_meter = gradient_meter_animation(peak_hold=1000) +vu_meter.color_source = fire_colors + +# Dynamic level from value provider +set audio_level = triangle(min_value=0, max_value=255, period=2s) +animation audio_meter = gradient_meter_animation(peak_hold=500) +audio_meter.color_source = rainbow +audio_meter.level = audio_level +``` + +**Factory**: `animation.gradient_meter_animation(engine)` + +### NoiseAnimation + +Creates pseudo-random noise patterns with configurable scale, speed, and fractal complexity. Perfect for organic, natural-looking effects like clouds, fire textures, or abstract patterns. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | instance | nil | - | Color provider for noise mapping (nil = rainbow) | +| `scale` | int | 50 | 1-255 | Noise scale/frequency (lower = larger patterns) | +| `speed` | int | 30 | 0-255 | Animation speed (0 = static pattern) | +| `octaves` | int | 1 | 1-4 | Number of noise octaves for fractal complexity | +| `persistence` | int | 128 | 0-255 | How much each octave contributes to final pattern | +| `seed` | int | 12345 | 0-65535 | Random seed for reproducible patterns | +| *(inherits all Animation parameters)* | | | | | + +#### Noise Characteristics + +**Scale Effects:** +- **Low scale (10-30)**: Large, flowing patterns +- **Medium scale (40-80)**: Balanced detail and flow +- **High scale (100-200)**: Fine, detailed textures + +**Octave Effects:** +- **1 octave**: Smooth, simple patterns +- **2 octaves**: Added medium-frequency detail +- **3+ octaves**: Complex, natural-looking textures + +**Speed Effects:** +- **Static (0)**: Fixed pattern for backgrounds +- **Slow (10-40)**: Gentle, organic movement +- **Fast (80-200)**: Dynamic, energetic patterns + +#### Usage Examples + +```berry +# Rainbow noise with medium detail +animation rainbow_noise = noise_animation( + scale=60, + speed=40, + octaves=1 +) + +# Blue fire texture with fractal detail +color blue_fire = 0xFF0066FF +animation blue_texture = noise_animation( + color=blue_fire, + scale=120, + speed=60, + octaves=3, + persistence=100 +) + +# Static cloud pattern +animation cloud_pattern = noise_animation( + color=white, + scale=30, + speed=0, + octaves=2 +) +``` + +#### Common Use Cases + +- **Ambient Lighting**: Slow, low-scale noise for background ambiance +- **Fire Effects**: Orange/red colors with medium scale and speed +- **Water Effects**: Blue/cyan colors with flowing movement +- **Cloud Simulation**: White/gray colors with large-scale patterns +- **Abstract Art**: Rainbow colors with high detail and multiple octaves + + + +### PulseAnimation + +Creates a pulsing effect oscillating between min and max brightness. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | Pulse color | +| `min_brightness` | int | 0 | 0-255 | Minimum brightness level | +| `max_brightness` | int | 255 | 0-255 | Maximum brightness level | +| `period` | int | 1000 | min: 100 | Pulse period in milliseconds | +| *(inherits all Animation parameters)* | | | | | + +**Factory**: `animation.pulsating_animation(engine)` + +### BeaconAnimation + +Creates a pulse effect at a specific position with optional fade regions. Inherits from `Animation`. + +#### Visual Pattern + +``` + pos (1) + | + v + _______ + / \ + _______/ \____________ + | | | | + |2| 3 |2| +``` + +Where: +1. `pos` - Start of the pulse (in pixels) +2. `slew_size` - Number of pixels to fade from back to fore color (can be 0) +3. `beacon_size` - Number of pixels of the pulse + +The pulse consists of: +- **Core pulse**: Full brightness region of `beacon_size` pixels +- **Fade regions**: Optional `slew_size` pixels on each side with gradual fade +- **Total width**: `beacon_size + (2 * slew_size)` pixels + +#### Parameters + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | Pulse color in ARGB format | +| `back_color` | int | 0xFF000000 | - | Background color in ARGB format | +| `pos` | int | 0 | - | Pulse start position in pixels | +| `beacon_size` | int | 1 | min: 0 | Size of core pulse in pixels | +| `slew_size` | int | 0 | min: 0 | Fade region size on each side in pixels | +| *(inherits all Animation parameters)* | | | | | + +#### Pattern Behavior + +- **Sharp Pulse** (`slew_size = 0`): Rectangular pulse with hard edges +- **Soft Pulse** (`slew_size > 0`): Pulse with smooth fade-in/fade-out regions +- **Positioning**: `pos` defines the start of the core pulse region +- **Fade Calculation**: Linear fade from full brightness to background color +- **Boundary Handling**: Fade regions are clipped to frame boundaries + +#### Usage Examples + +```berry +# Sharp pulse at center +animation sharp_pulse = beacon_animation( + color=red, + pos=10, + beacon_size=3, + slew_size=0 +) + +# Soft pulse with fade regions +animation soft_pulse = beacon_animation( + color=green, + pos=5, + beacon_size=2, + slew_size=3 +) +# Total width: 2 + (2 * 3) = 8 pixels + +# Spotlight effect +color dark_blue = 0xFF000040 +animation spotlight = beacon_animation( + color=white, + back_color=dark_blue, + pos=15, + beacon_size=1, + slew_size=5 +) + +run spotlight +``` + +#### Common Use Cases + +**Spotlight Effects:** +```berry +# Moving spotlight with soft edges +animation moving_spotlight = beacon_animation( + color=white, + back_color=0xFF000040, + beacon_size=1, + slew_size=5 +) +moving_spotlight.pos = triangle(min_value=0, max_value=29, period=3s) +``` + +**Position Markers:** +```berry +# Sharp position marker +animation position_marker = beacon_animation( + color=red, + pos=15, + beacon_size=1, + slew_size=0 +) +``` + +**Breathing Spots:** +```berry +# Breathing effect at specific position +animation breathing_spot = beacon_animation( + color=blue, + pos=10, + beacon_size=3, + slew_size=2 +) +breathing_spot.opacity = smooth(min_value=50, max_value=255, period=2s) +``` + +**Factory**: `animation.beacon_animation(engine)` + +### CrenelPositionAnimation + +Creates a crenel (square wave) pattern with repeating rectangular pulses. Inherits from `Animation`. + +#### Visual Pattern + +``` + pos (1) + | + v (*4) + ______ ____ + | | | + _________| |_________| + + | 2 | 3 | +``` + +Where: +1. `pos` - Starting position of the first pulse (in pixels) +2. `pulse_size` - Width of each pulse (in pixels) +3. `low_size` - Gap between pulses (in pixels) +4. `nb_pulse` - Number of pulses (-1 for infinite) + +The full period of the pattern is `pulse_size + low_size` pixels. + +#### Parameters + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | Pulse color in ARGB format | +| `back_color` | int | 0xFF000000 | - | Background color in ARGB format | +| `pos` | int | 0 | - | Starting position of first pulse in pixels | +| `pulse_size` | int | 1 | min: 0 | Width of each pulse in pixels | +| `low_size` | int | 3 | min: 0 | Gap between pulses in pixels | +| `nb_pulse` | int | -1 | - | Number of pulses (-1 = infinite) | +| *(inherits all Animation parameters)* | | | | | + +#### Pattern Behavior + +- **Infinite Mode** (`nb_pulse = -1`): Pattern repeats continuously across the strip +- **Finite Mode** (`nb_pulse > 0`): Shows exactly the specified number of pulses +- **Period**: Each pattern cycle spans `pulse_size + low_size` pixels +- **Boundary Handling**: Pulses are clipped to frame boundaries +- **Zero Sizes**: `pulse_size = 0` produces no output; `low_size = 0` creates continuous pulses + +#### Pattern Calculations + +- **Period and Positioning**: The pattern repeats every `pulse_size + low_size` pixels +- **Optimization**: For infinite pulses, the algorithm calculates optimal starting position for efficient rendering +- **Boundary Clipping**: Pulses are automatically clipped to frame boundaries +- **Modulo Arithmetic**: Negative positions are handled correctly with modulo arithmetic + +#### Common Use Cases + +**Status Indicators:** +```berry +# Slow blinking pattern for status indication +animation status_indicator = crenel_animation( + color=green, + pulse_size=1, + low_size=9 +) +``` + +**Rhythmic Effects:** +```berry +# Fast rhythmic pattern +animation rhythm_pattern = crenel_animation( + color=red, + pulse_size=2, + low_size=2 +) +``` + +**Decorative Borders:** +```berry +# Decorative border pattern +color gold = 0xFFFFD700 +animation border_pattern = crenel_animation( + color=gold, + pulse_size=3, + low_size=1, + nb_pulse=10 +) +``` + +**Progress Indicators:** +```berry +# Progress bar with limited pulses +animation progress_bar = crenel_animation( + color=0xFF0080FF, + pulse_size=2, + low_size=1, + nb_pulse=5 +) +``` + +#### Integration Features + +- **Parameter System**: All parameters support dynamic updates with validation +- **Method Chaining**: Fluent API for configuration (in Berry code) +- **Animation Lifecycle**: Inherits standard animation lifecycle methods +- **Framework Integration**: Seamless integration with animation engine +- **Testing**: Comprehensive test suite covering edge cases and performance + +**Factory**: `animation.crenel_animation(engine)` + +### RichPaletteAnimation + +Creates smooth color transitions using rich palette data with direct parameter access. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `colors` | bytes | rainbow palette | - | Palette bytes or predefined palette | +| `period` | int | 5000 | min: 0 | Cycle time in ms (0 = value-based only) | +| `transition_type` | int | animation.LINEAR | enum: [animation.LINEAR, animation.SINE] | LINEAR=constant speed, SINE=smooth ease-in/ease-out | +| `brightness` | int | 255 | 0-255 | Overall brightness scaling | +| *(inherits all Animation parameters)* | | | | | + +**Special Features**: +- Direct parameter access (set `anim.colors` instead of `anim.color.colors`) +- Parameters are automatically forwarded to internal `RichPaletteColorProvider` +- Access to specialized methods via `anim.color_provider.method_name()` + +**Factory**: `animation.rich_palette_animation(engine)` + +### TwinkleAnimation + +Creates a twinkling stars effect with random lights appearing and fading. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFFFFFF | - | Twinkle color | +| `density` | int | 128 | 0-255 | Twinkle density/probability | +| `twinkle_speed` | int | 6 | 1-5000 | Update frequency in Hz (or period in ms if ≥50) | +| `fade_speed` | int | 180 | 0-255 | How quickly twinkles fade | +| `min_brightness` | int | 32 | 0-255 | Minimum twinkle brightness | +| `max_brightness` | int | 255 | 0-255 | Maximum twinkle brightness | +| *(inherits all Animation parameters)* | | | | | + +**Factories**: `animation.twinkle_animation(engine)`, `animation.twinkle_classic(engine)`, `animation.twinkle_solid(engine)`, `animation.twinkle_rainbow(engine)`, `animation.twinkle_gentle(engine)`, `animation.twinkle_intense(engine)` + +### WaveAnimation + +Creates mathematical waveforms that can move along the LED strip. Perfect for rhythmic patterns, breathing effects, or mathematical visualizations. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color` | int | 0xFFFF0000 | - | Wave color | +| `back_color` | int | 0xFF000000 | - | Background color shown in wave valleys | +| `wave_type` | int | 0 | 0-3 | 0=sine, 1=triangle, 2=square, 3=sawtooth | +| `amplitude` | int | 128 | 0-255 | Wave height/intensity range | +| `frequency` | int | 32 | 0-255 | How many wave cycles fit on the strip | +| `phase` | int | 0 | 0-255 | Horizontal wave pattern shift | +| `wave_speed` | int | 50 | 0-255 | Movement speed (0 = static wave) | +| `center_level` | int | 128 | 0-255 | Baseline intensity around which wave oscillates | +| *(inherits all Animation parameters)* | | | | | + +#### Wave Types + +**Sine Wave (0):** +- **Characteristics**: Smooth, natural oscillation +- **Best for**: Breathing effects, natural rhythms, ambient lighting + +**Triangle Wave (1):** +- **Characteristics**: Linear ramps up and down with sharp peaks +- **Best for**: Scanning effects, linear fades + +**Square Wave (2):** +- **Characteristics**: Sharp on/off transitions +- **Best for**: Strobing, digital effects, alerts + +**Sawtooth Wave (3):** +- **Characteristics**: Gradual rise, instant drop +- **Best for**: Scanning beams, ramp effects + +#### Wave Characteristics + +**Frequency Effects:** +- **Low frequency (10-30)**: Long, flowing waves +- **Medium frequency (40-80)**: Balanced wave patterns +- **High frequency (100-200)**: Dense, detailed patterns + +**Amplitude Effects:** +- **Low amplitude (50-100)**: Subtle intensity variation +- **Medium amplitude (100-180)**: Noticeable wave pattern +- **High amplitude (200-255)**: Dramatic intensity swings + +#### Usage Examples + +```berry +# Rainbow sine wave +animation rainbow_wave = wave_animation( + wave_type=0, + frequency=40, + wave_speed=80, + amplitude=150 +) + +# Green breathing effect +animation breathing = wave_animation( + color=green, + wave_type=0, + amplitude=150, + frequency=20, + wave_speed=30 +) + +# Fast square wave strobe +animation strobe = wave_animation( + color=white, + wave_type=2, + frequency=80, + wave_speed=150 +) +``` + +#### Common Use Cases + +- **Breathing Effects**: Slow sine waves for calming ambiance +- **Scanning Beams**: Sawtooth waves for radar-like effects +- **Strobing**: Square waves for attention-getting flashes +- **Color Cycling**: Rainbow waves for spectrum effects +- **Pulse Patterns**: Triangle waves for rhythmic pulses + + + +### PaletteGradientAnimation + +Creates shifting gradient patterns with palette colors. Inherits from `Animation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `color_source` | instance | nil | - | Color provider for pattern mapping | +| `shift_period` | int | 0 | min: 0 | Time for one complete shift cycle in ms (0 = static gradient) | +| `spatial_period` | int | 0 | min: 0 | Spatial period in pixels (0 = full strip length) | +| `phase_shift` | int | 0 | 0-255 | Phase shift in 0-255 range (mapped to spatial period) | +| *(inherits all Animation parameters)* | | | | | + +**Implementation Details:** +- Uses `bytes()` buffer for efficient storage of per-pixel values +- Color source receives values in 0-255 range via `get_color_for_value(value, time_ms)` +- Buffer automatically resizes when strip length changes +- Optimized LUT (Lookup Table) support for color providers + +**Pattern Generation:** +- Generates linear gradient values in 0-255 range across the specified spatial period +- **shift_period**: Controls temporal movement - how long it takes for the gradient to shift one full spatial period + - `0`: Static gradient (no movement) + - `> 0`: Moving gradient with specified period in milliseconds +- **spatial_period**: Controls spatial repetition - how many pixels before the gradient pattern repeats + - `0`: Gradient spans the full strip length (single gradient across entire strip) + - `> 0`: Gradient repeats every N pixels +- **phase_shift**: Shifts the gradient pattern spatially by a percentage of the spatial period +- Each pixel's value calculated using optimized fixed-point arithmetic + +**Factory**: `animation.palette_gradient_animation(engine)` + +### PaletteMeterAnimation + +Creates meter/bar patterns based on a value function. Inherits from `PaletteGradientAnimation`. + +| Parameter | Type | Default | Constraints | Description | +|-----------|------|---------|-------------|-------------| +| `value_func` | function | nil | - | Function that provides meter values (0-255 range) | +| *(inherits all PaletteGradientAnimation parameters)* | | | | | + +**Pattern Generation:** +- Value function signature: `value_func(engine, time_ms, self)` where: + - `engine`: AnimationEngine reference + - `time_ms`: Elapsed time since animation start + - `self`: Reference to the animation instance +- Value function returns value in 0-255 range representing meter level +- Pixels within meter range get value 255, others get value 0 +- Meter position calculated as: `position = tasmota.scale_uint(value, 0, 255, 0, strip_length)` + +**Factory**: `animation.palette_meter_animation(engine)` + +## Motion Effects + +Motion effects are transformation animations that apply movement, scaling, and distortion to existing animations. They accept any animation as a source and can be chained together for complex effects. + +### Combining Motion Effects + +Motion effects can be chained to create sophisticated transformations: + +```berry +# Base animation +animation base_pulse = pulsating_animation(color=blue, period=3s) + +# Simple animation composition +animation fire_effect = fire_animation( + color=fire_colors, + intensity=180, + flicker_speed=8 +) + +animation gradient_wave = gradient_animation( + color=rainbow_cycle, + gradient_type=0, + movement_speed=50 +) + +# Result: Multiple independent animations +run base_pulse +run fire_effect +run gradient_wave +``` + +### Performance Considerations + +- Each animation uses approximately 4 bytes per pixel for color storage +- Fire animation includes additional flicker calculations +- Gradient animation requires color interpolation calculations +- Noise animation includes pseudo-random pattern generation +- Consider strip length impact on transformation calculations + +## Parameter Constraints + +### Constraint Types + +| Constraint | Type | Description | Example | +|------------|------|-------------|---------| +| `default` | any | Default value used during initialization | `"default": 50` | +| `min` | int | Minimum allowed value for integers | `"min": 0` | +| `max` | int | Maximum allowed value for integers | `"max": 100` | +| `enum` | list | List of valid values | `"enum": [1, 2, 3]` | +| `type` | string | Expected value type | `"type": "string"` | +| `nillable` | bool | Whether nil values are allowed | `"nillable": true` | + +### Supported Types + +| Type | Description | +|------|-------------| +| `"int"` | Integer values (default if not specified) | +| `"string"` | String values | +| `"bool"` | Boolean values (true/false) | +| `"instance"` | Object instances | +| `"any"` | Any type (no type validation) | + +### Factory Function Rules + +1. **Engine-Only Parameters**: All factory functions take ONLY the `engine` parameter +2. **No Redundant Factories**: If a factory only calls the constructor, export the class directly +3. **Preset Factories**: Factory functions should provide useful presets or complex configurations +4. **Parameter Assignment**: Set parameters via virtual member assignment after creation \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Animation_Development.md b/lib/libesp32/berry_animation/animation_docs/Animation_Development.md new file mode 100644 index 000000000..990328d53 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Animation_Development.md @@ -0,0 +1,695 @@ +# Animation Development Guide + +Guide for developers creating custom animation classes in the Berry Animation Framework. + +## Overview + +**Note**: This guide is for developers who want to extend the framework by creating new animation classes. For using existing animations, see the [DSL Reference](Dsl_Reference.md) which provides a declarative way to create animations without programming. + +The Berry Animation Framework uses a unified architecture where all visual elements inherit from the base `Animation` class. This guide explains how to create custom animation classes that integrate seamlessly with the framework's parameter system, value providers, and rendering pipeline. + +## Animation Class Structure + +### Basic Class Template + +```berry +#@ solidify:MyAnimation,weak +class MyAnimation : animation.animation + # NO instance variables for parameters - they are handled by the virtual parameter system + + # Parameter definitions following the new specification + static var PARAMS = { + "my_param1": {"default": "default_value", "type": "string"}, + "my_param2": {"min": 0, "max": 255, "default": 100, "type": "int"} + # Do NOT include inherited Animation parameters here + } + + def init(engine) + # Engine parameter is MANDATORY and cannot be nil + super(self).init(engine) + + # Only initialize non-parameter instance variables (none in this example) + # Parameters are handled by the virtual parameter system + end + + # Handle parameter changes (optional) + def on_param_changed(name, value) + # Add custom logic for parameter changes if needed + # Parameter validation is handled automatically by the framework + end + + # Update animation state (no return value needed) + def update(time_ms) + super(self).update(time_ms) + # Your update logic here + end + + def render(frame, time_ms, strip_length) + if !self.is_running || frame == nil + return false + end + + # Use virtual parameter access - automatically resolves ValueProviders + var param1 = self.my_param1 + var param2 = self.my_param2 + + # Use strip_length parameter instead of self.engine.strip_length for performance + # Your rendering logic here + # ... + + return true + end + + # NO setter methods needed - use direct virtual parameter assignment: + # obj.my_param1 = value + # obj.my_param2 = value + + def tostring() + return f"MyAnimation(param1={self.my_param1}, param2={self.my_param2}, running={self.is_running})" + end +end +``` + +## PARAMS System + +### Static Parameter Definition + +The `PARAMS` static variable defines all parameters specific to your animation class. This system provides: + +- **Parameter validation** with min/max constraints and type checking +- **Default value handling** for initialization +- **Virtual parameter access** through getmember/setmember +- **Automatic ValueProvider resolution** + +#### Parameter Definition Format + +```berry +static var PARAMS = { + "parameter_name": { + "default": default_value, # Default value (optional) + "min": minimum_value, # Minimum value for integers (optional) + "max": maximum_value, # Maximum value for integers (optional) + "enum": [val1, val2, val3], # Valid enum values (optional) + "type": "parameter_type", # Expected type (optional) + "nillable": true # Whether nil values are allowed (optional) + } +} +``` + +#### Supported Types + +- **`"int"`** - Integer values (default if not specified) +- **`"string"`** - String values +- **`"bool"`** - Boolean values (true/false) +- **`"bytes"`** - Bytes objects (validated using isinstance()) +- **`"instance"`** - Object instances +- **`"any"`** - Any type (no type validation) + +#### Important Rules + +- **Do NOT include inherited parameters** - Animation base class parameters are handled automatically +- **Only define class-specific parameters** in your PARAMS +- **No constructor parameter mapping** - the new system uses engine-only constructors +- **Parameters are accessed via virtual members**: `obj.param_name` + +## Constructor Implementation + +### Engine-Only Constructor Pattern + +```berry +def init(engine) + # 1. ALWAYS call super with engine (engine is the ONLY parameter) + super(self).init(engine) + + # 2. Initialize non-parameter instance variables only + self.internal_state = initial_value + self.buffer = nil + # Do NOT initialize parameters here - they are handled by the virtual system +end +``` + +### Parameter Change Handling + +```berry +def on_param_changed(name, value) + # Optional method to handle parameter changes + if name == "scale" + # Recalculate internal state when scale changes + self._update_internal_buffers() + elif name == "color" + # Handle color changes + self._invalidate_color_cache() + end +end +``` + +### Key Changes from Old System + +- **Engine-only constructor**: Constructor takes ONLY the engine parameter +- **No parameter initialization**: Parameters are set by caller using virtual member assignment +- **No instance variables for parameters**: Parameters are handled by the virtual system +- **Automatic validation**: Parameter validation happens automatically based on PARAMS constraints + +## Value Provider Integration + +### Automatic ValueProvider Resolution + +The virtual parameter system automatically resolves ValueProviders when you access parameters: + +```berry +def render(frame, time_ms, strip_length) + # Virtual parameter access automatically resolves ValueProviders + var color = self.color # Returns current color value, not the provider + var position = self.pos # Returns current position value + var size = self.size # Returns current size value + + # Use strip_length parameter (computed once by engine_proxy) instead of self.engine.strip_length + # Use resolved values in rendering logic + for i: position..(position + size - 1) + if i >= 0 && i < strip_length + frame.set_pixel_color(i, color) + end + end + + return true +end +``` + +### Setting Dynamic Parameters + +Users can set both static values and ValueProviders using the same syntax: + +```berry +# Create animation +var anim = animation.my_animation(engine) + +# Static values +anim.color = 0xFFFF0000 +anim.pos = 5 +anim.size = 3 + +# Dynamic values +anim.color = animation.smooth(0xFF000000, 0xFFFFFFFF, 2000) +anim.pos = animation.triangle(0, 29, 3000) +``` + +### Performance Optimization + +For performance-critical code, cache parameter values: + +```berry +def render(frame, time_ms, strip_length) + # Cache parameter values to avoid multiple virtual member access + var current_color = self.color + var current_pos = self.pos + var current_size = self.size + + # Use cached values in loops + for i: current_pos..(current_pos + current_size - 1) + if i >= 0 && i < strip_length + frame.set_pixel_color(i, current_color) + end + end + + return true +end +``` + +### Color Provider LUT Optimization + +For color providers that perform expensive color calculations (like palette interpolation), the base `ColorProvider` class provides a Lookup Table (LUT) mechanism for caching pre-computed colors: + +```berry +#@ solidify:MyColorProvider,weak +class MyColorProvider : animation.color_provider + # Instance variables (all should start with underscore) + var _cached_data # Your custom cached data + + def init(engine) + super(self).init(engine) # Initializes _color_lut and _lut_dirty + self._cached_data = nil + end + + # Mark LUT as dirty when parameters change + def on_param_changed(name, value) + super(self).on_param_changed(name, value) + if name == "colors" || name == "transition_type" + self._lut_dirty = true # Inherited from ColorProvider + end + end + + # Rebuild LUT when needed + def _rebuild_color_lut() + # Allocate LUT (e.g., 129 entries * 4 bytes = 516 bytes) + if self._color_lut == nil + self._color_lut = bytes() + self._color_lut.resize(129 * 4) + end + + # Pre-compute colors for values 0, 2, 4, ..., 254, 255 + var i = 0 + while i < 128 + var value = i * 2 + var color = self._compute_color_expensive(value) + self._color_lut.set(i * 4, color, 4) + i += 1 + end + + # Add final entry for value 255 + var color_255 = self._compute_color_expensive(255) + self._color_lut.set(128 * 4, color_255, 4) + + self._lut_dirty = false + end + + # Update method checks if LUT needs rebuilding + def update(time_ms) + if self._lut_dirty || self._color_lut == nil + self._rebuild_color_lut() + end + return self.is_running + end + + # Fast color lookup using LUT + def get_color_for_value(value, time_ms) + # Build LUT if needed (lazy initialization) + if self._lut_dirty || self._color_lut == nil + self._rebuild_color_lut() + end + + # Map value to LUT index (divide by 2, special case for 255) + var lut_index = value >> 1 + if value >= 255 + lut_index = 128 + end + + # Retrieve pre-computed color from LUT + var color = self._color_lut.get(lut_index * 4, 4) + + # Apply brightness scaling using static method (only if not 255) + var brightness = self.brightness + if brightness != 255 + return animation.color_provider.apply_brightness(color, brightness) + end + + return color + end + + # Access LUT from outside (returns bytes() or nil) + # Inherited from ColorProvider: get_lut() +end +``` + +**LUT Benefits:** +- **5-10x speedup** for expensive color calculations +- **Reduced CPU usage** during rendering +- **Smooth animations** even with complex color logic +- **Memory efficient** (typically 516 bytes for 129 entries) + +**When to use LUT:** +- Palette interpolation with binary search +- Complex color transformations +- Brightness calculations +- Any expensive per-pixel color computation + +**LUT Guidelines:** +- Store colors at maximum brightness, apply scaling after lookup +- Use 2-step resolution (0, 2, 4, ..., 254, 255) to save memory +- Invalidate LUT when parameters affecting color calculation change +- Don't invalidate for brightness changes if brightness is applied post-lookup + +**Brightness Handling:** + +The `ColorProvider` base class includes a `brightness` parameter (0-255, default 255) and a static method for applying brightness scaling: + +```berry +# Static method for brightness scaling (only scales if brightness != 255) +animation.color_provider.apply_brightness(color, brightness) +``` + +**Best Practices:** +- Store LUT colors at maximum brightness (255) +- Apply brightness scaling after LUT lookup using the static method +- Only call the static method if `brightness != 255` to avoid unnecessary overhead +- For inline performance-critical code, you can inline the brightness calculation instead of calling the static method +- Brightness changes do NOT invalidate the LUT since brightness is applied after lookup + +## Parameter Access + +### Direct Virtual Member Assignment + +The new system uses direct parameter assignment instead of setter methods: + +```berry +# Create animation +var anim = animation.my_animation(engine) + +# Direct parameter assignment (recommended) +anim.color = 0xFF00FF00 +anim.pos = 10 +anim.size = 5 + +# Method chaining is not needed - just set parameters directly +``` + +### Parameter Validation + +The parameter system handles validation automatically based on PARAMS constraints: + +```berry +# This will raise an exception due to min: 0 constraint +anim.size = -1 # Raises value_error + +# This will be accepted +anim.size = 5 # Parameter updated successfully + +# Method-based setting returns true/false for validation +var success = anim.set_param("size", -1) # Returns false, no exception +``` + +### Accessing Raw Parameters + +```berry +# Get current parameter value (resolved if ValueProvider) +var current_color = anim.color + +# Get raw parameter (returns ValueProvider if set) +var raw_color = anim.get_param("color") + +# Check if parameter is a ValueProvider +if animation.is_value_provider(raw_color) + print("Color is dynamic") +else + print("Color is static") +end +``` + +## Rendering Implementation + +### Frame Buffer Operations + +```berry +def render(frame, time_ms, strip_length) + if !self.is_running || frame == nil + return false + end + + # Resolve dynamic parameters + var color = self.resolve_value(self.color, "color", time_ms) + var opacity = self.resolve_value(self.opacity, "opacity", time_ms) + + # Render your effect using strip_length parameter + for i: 0..(strip_length-1) + var pixel_color = calculate_pixel_color(i, time_ms) + frame.set_pixel_color(i, pixel_color) + end + + # Apply opacity if not full (supports numbers, animations) + if opacity < 255 + frame.apply_opacity(opacity) + end + + return true # Frame was modified +end +``` + +### Common Rendering Patterns + +#### Fill Pattern +```berry +# Fill entire frame with color +frame.fill_pixels(color) +``` + +#### Position-Based Effects +```berry +# Render at specific positions +var start_pos = self.resolve_value(self.pos, "pos", time_ms) +var size = self.resolve_value(self.size, "size", time_ms) + +for i: 0..(size-1) + var pixel_pos = start_pos + i + if pixel_pos >= 0 && pixel_pos < frame.width + frame.set_pixel_color(pixel_pos, color) + end +end +``` + +#### Gradient Effects +```berry +# Create gradient across frame +for i: 0..(frame.width-1) + var progress = i / (frame.width - 1.0) # 0.0 to 1.0 + var interpolated_color = interpolate_color(start_color, end_color, progress) + frame.set_pixel_color(i, interpolated_color) +end +``` + +## Complete Example: BeaconAnimation + +Here's a complete example showing all concepts: + +```berry +#@ solidify:BeaconAnimation,weak +class BeaconAnimation : animation.animation + # NO instance variables for parameters - they are handled by the virtual parameter system + + # Parameter definitions following the new specification + static var PARAMS = { + "color": {"default": 0xFFFFFFFF}, + "back_color": {"default": 0xFF000000}, + "pos": {"default": 0}, + "beacon_size": {"min": 0, "default": 1}, + "slew_size": {"min": 0, "default": 0} + } + + # Initialize a new Pulse Position animation + # Engine parameter is MANDATORY and cannot be nil + def init(engine) + # Call parent constructor with engine (engine is the ONLY parameter) + super(self).init(engine) + + # Only initialize non-parameter instance variables (none in this case) + # Parameters are handled by the virtual parameter system + end + + # Handle parameter changes (optional - can be removed if no special handling needed) + def on_param_changed(name, value) + # No special handling needed for this animation + # Parameter validation is handled automatically by the framework + end + + # Render the pulse to the provided frame buffer + def render(frame, time_ms, strip_length) + if frame == nil + return false + end + + var pixel_size = strip_length + # Use virtual parameter access - automatically resolves ValueProviders + var back_color = self.back_color + var pos = self.pos + var slew_size = self.slew_size + var beacon_size = self.beacon_size + var color = self.color + + # Fill background if not transparent + if back_color != 0xFF000000 + frame.fill_pixels(back_color) + end + + # Calculate pulse boundaries + var pulse_min = pos + var pulse_max = pos + beacon_size + + # Clamp to frame boundaries + if pulse_min < 0 + pulse_min = 0 + end + if pulse_max >= pixel_size + pulse_max = pixel_size + end + + # Draw the main pulse + var i = pulse_min + while i < pulse_max + frame.set_pixel_color(i, color) + i += 1 + end + + # Draw slew regions if slew_size > 0 + if slew_size > 0 + # Left slew (fade from background to pulse color) + var left_slew_min = pos - slew_size + var left_slew_max = pos + + if left_slew_min < 0 + left_slew_min = 0 + end + if left_slew_max >= pixel_size + left_slew_max = pixel_size + end + + i = left_slew_min + while i < left_slew_max + # Calculate blend factor + var blend_factor = tasmota.scale_uint(i, pos - slew_size, pos - 1, 255, 0) + var alpha = 255 - blend_factor + var blend_color = (alpha << 24) | (color & 0x00FFFFFF) + var blended_color = frame.blend(back_color, blend_color) + frame.set_pixel_color(i, blended_color) + i += 1 + end + + # Right slew (fade from pulse color to background) + var right_slew_min = pos + beacon_size + var right_slew_max = pos + beacon_size + slew_size + + if right_slew_min < 0 + right_slew_min = 0 + end + if right_slew_max >= pixel_size + right_slew_max = pixel_size + end + + i = right_slew_min + while i < right_slew_max + # Calculate blend factor + var blend_factor = tasmota.scale_uint(i, pos + beacon_size, pos + beacon_size + slew_size - 1, 0, 255) + var alpha = 255 - blend_factor + var blend_color = (alpha << 24) | (color & 0x00FFFFFF) + var blended_color = frame.blend(back_color, blend_color) + frame.set_pixel_color(i, blended_color) + i += 1 + end + end + + return true + end + + # NO setter methods - use direct virtual parameter assignment instead: + # obj.color = value + # obj.pos = value + # obj.beacon_size = value + # obj.slew_size = value + + # String representation of the animation + def tostring() + return f"BeaconAnimation(color=0x{self.color :08x}, pos={self.pos}, beacon_size={self.beacon_size}, slew_size={self.slew_size})" + end +end + +# Export class directly - no redundant factory function needed +return {'beacon_animation': BeaconAnimation} +``` + +## Testing Your Animation + +### Unit Tests + +Create comprehensive tests for your animation: + +```berry +import animation + +def test_my_animation() + # Create LED strip and engine for testing + var strip = global.Leds(10) # Use built-in LED strip for testing + var engine = animation.create_engine(strip) + + # Test basic construction + var anim = animation.my_animation(engine) + assert(anim != nil, "Animation should be created") + + # Test parameter setting + anim.color = 0xFFFF0000 + assert(anim.color == 0xFFFF0000, "Color should be set") + + # Test parameter updates + anim.color = 0xFF00FF00 + assert(anim.color == 0xFF00FF00, "Color should be updated") + + # Test value providers + var dynamic_color = animation.smooth(engine) + dynamic_color.min_value = 0xFF000000 + dynamic_color.max_value = 0xFFFFFFFF + dynamic_color.duration = 2000 + + anim.color = dynamic_color + var raw_color = anim.get_param("color") + assert(animation.is_value_provider(raw_color), "Should accept value provider") + + # Test rendering + var frame = animation.frame_buffer(10) + anim.start() + var result = anim.render(frame, 1000, engine.strip_length) + assert(result == true, "Should render successfully") + + print("✓ All tests passed") +end + +test_my_animation() +``` + +### Integration Testing + +Test with the animation engine: + +```berry +var strip = global.Leds(30) # Use built-in LED strip +var engine = animation.create_engine(strip) +var anim = animation.my_animation(engine) + +# Set parameters +anim.color = 0xFFFF0000 +anim.pos = 5 +anim.beacon_size = 3 + +engine.add(anim) # Unified method for animations and sequence managers +engine.run() + +# Let it run for a few seconds +tasmota.delay(3000) + +engine.stop() +print("Integration test completed") +``` + +## Best Practices + +### Performance +- **Minimize calculations** in render() method +- **Cache resolved values** when possible +- **Use integer math** instead of floating point +- **Avoid memory allocation** in render loops + +### Memory Management +- **Reuse objects** when possible +- **Clear references** to large objects when done +- **Use static variables** for constants + +### Code Organization +- **Group related parameters** together +- **Use descriptive variable names** +- **Comment complex algorithms** +- **Follow Berry naming conventions** + +### Error Handling +- **Validate parameters** in constructor +- **Handle edge cases** gracefully +- **Return false** from render() on errors +- **Use meaningful error messages** + +## Publishing Your Animation Class + +Once you've created a new animation class: + +1. **Add it to the animation module** by importing it in `animation.be` +2. **Create a factory function** following the engine-first pattern +3. **Add DSL support** by ensuring the transpiler recognizes your factory function +4. **Document parameters** in the class hierarchy documentation +5. **Test with DSL** to ensure users can access your animation declaratively + +**Remember**: Users should primarily interact with animations through the DSL. The programmatic API is mainly for framework development and advanced integrations. + +This guide provides everything needed to create professional-quality animation classes that integrate seamlessly with the Berry Animation Framework's parameter system and rendering pipeline. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Animation_Tutorial.md b/lib/libesp32/berry_animation/animation_docs/Animation_Tutorial.md new file mode 100644 index 000000000..5336ff9e4 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Animation_Tutorial.md @@ -0,0 +1,1366 @@ +# Animation DSL Tutorial + +!!! tip "Try it online!" + You can test all examples in this tutorial directly in your browser using the **[Online Emulator](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html)**. No hardware required! All tutorial examples are available in the **Examples** dropdown menu. + +This tutorial will guide you through the Berry Animation DSL, from your first solid color to complex animated sequences with templates. Each chapter builds on the previous one, introducing new concepts progressively. + +## Table of Contents + +- [Overview](#overview) +- [Chapter 1: Getting Started](#chapter-1-getting-started) +- [Chapter 2: Color Cycling with Palettes](#chapter-2-color-cycling-with-palettes) +- [Chapter 3: Smooth Color Transitions](#chapter-3-smooth-color-transitions) +- [Chapter 4: Spatial Patterns and Gradients](#chapter-4-spatial-patterns-and-gradients) +- [Chapter 5: Beacons and Moving Effects](#chapter-5-beacons-and-moving-effects) +- [Chapter 6: Shutters and Sequences](#chapter-6-shutters-and-sequences) +- [Chapter 7: Crenel Patterns](#chapter-7-crenel-patterns) +- [Chapter 8: Templates for Reusable Animations](#chapter-8-templates-for-reusable-animations) +- [Quick Reference Card](#quick-reference-card) + +--- + +## Overview + +The Animation DSL (Domain-Specific Language) lets you create LED strip animations using simple, declarative syntax. Instead of writing complex Berry code with timing loops and state machines, you describe *what* you want and *how long* it should take. + +### Why Use the DSL? + +Traditional LED programming requires managing frame buffers, timing loops, and complex state machines. The Animation DSL abstracts all of this away: + +- **No timing code** - Just specify durations like `period=2s` and the framework handles the rest +- **No state machines** - Animations automatically manage their internal state +- **Composable** - Layer multiple animations, use one as an opacity mask for another +- **Readable** - Code reads almost like English: "animation pulse = breathe_animation(color=red, period=2s)" + +### Key Concepts + +Before diving into code, let's understand the building blocks: + +| Concept | What It Does | Example | +|---------|--------------|---------| +| **Animation** | A visual effect on the LED strip | `solid`, `twinkle_animation`, `beacon_animation` | +| **Color** | Either a static value or a dynamic provider that changes over time | `red`, `0xFF0000`, `color_cycle(...)` | +| **Palette** | A collection of colors for gradients or cycling | `PALETTE_RAINBOW`, custom arrays | +| **Value Provider** | A number that changes over time (oscillates) | `sine_osc`, `triangle`, `sawtooth` | +| **Sequence** | Orchestrates animations with timing control | `play`, `wait`, `repeat` | +| **Template** | A reusable animation pattern with parameters | `template animation cylon_eye { ... }` | + +### How It Works + +1. You write DSL code in a `.anim` file +2. The transpiler converts it to Berry code +3. Berry code runs on your ESP32 (or in the browser emulator) +4. The animation engine renders frames to your LED strip + +--- + +## Chapter 1: Getting Started + +This chapter covers the basics: creating simple animations, using colors, and layering effects. By the end, you'll understand how to combine multiple animations into a scene. + +### 1.1 Your First Animation: Solid Color + +Solid Red + +Every animation starts with two steps: **define** it, then **run** it. The simplest animation fills the entire strip with a single color. + +```berry +# Solid red background - the simplest animation + +animation back = solid(color=red) +run back +``` + +Let's break this down: + +- `animation back` - Creates a new animation and names it "back" +- `solid(color=red)` - Uses the `solid` animation type, which fills all LEDs with one color +- `run back` - Starts the animation running + +The DSL provides many predefined colors: `red`, `green`, `blue`, `white`, `yellow`, `orange`, `purple`, `cyan`, and more. + +### 1.2 Custom Colors + +Custom Color + +Predefined colors are convenient, but you'll often want specific shades. Define custom colors using hexadecimal values. + +```berry +# Solid dark blue - using a custom color + +color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added +animation back = solid(color=space_blue) +run back +``` + +**Color format:** Colors include an alpha channel (transparency) in addition to RGB: + +- **6-digit format** `0xRRGGBB` - The alpha is automatically set to `FF` (fully opaque) +- **8-digit format** `0xAARRGGBB` - You specify the alpha explicitly + +The alpha channel ranges from `00` (fully transparent) to `FF` (fully opaque). Examples: + +- `0xFF0000` = pure red (actually stored as `0xFFFF0000`) +- `0x00FF00` = pure green (actually stored as `0xFF00FF00`) +- `0x000066` = dark blue, fully opaque +- `0x80FF0000` = semi-transparent red (50% opacity) + +The DSL also provides `transparent` as a predefined color, equivalent to `0x00000000` (fully transparent black). This is useful for backgrounds or opacity masks where you want parts to be invisible. + +### 1.3 Using Predefined Animations + +Twinkle Stars + +Beyond `solid`, the DSL includes many ready-to-use animation types. Each creates a different visual effect. Let's try `twinkle_animation`, which creates a twinkling stars effect. + +```berry +# Twinkle stars - using predefined animations + +animation stars = twinkle_animation() +run stars +``` + +With no parameters, animations use sensible defaults. The twinkle effect will use white sparkles at a moderate density. But you can customize everything! + +### 1.4 Animation Parameters + +Twinkle Parameters + +Most animations accept **parameters** to customize their behavior. Parameters use the `name=value` syntax inside the parentheses. + +```berry +# Twinkle stars with parameters - using animation parameters + +# Note: when parameters are in separate lines, you don't need a comma ',' +animation stars = twinkle_animation( + color=0xFFFFAA # Light yellow sparkles + density=8 # density (moderate sparkles) + twinkle_speed=100ms # twinkle speed + fade_speed=50 # when no unit, time unit is 'ms' +) +run stars +``` + +!!! tip "Parameter Syntax" + When parameters are on separate lines, commas are optional. This makes the code more readable for complex animations. + +**Time values** can use units (internally converted to milliseconds): + +- `500ms` - milliseconds (stays as 500) +- `2s` - seconds (converted to 2000) +- `1m` - minutes (converted to 60000) + +### 1.5 Layering Animations + +Twinkle Night + +One of the most powerful features is **layering** - running multiple animations simultaneously. Each animation has a `priority` that determines its rendering order. Think of it like layers in an image editor: lower numbers are "on top" and can obscure higher numbers. + +```berry +# Twinkle night - twinkle stars over a dark night + +# Dark blue background +color space_blue = 0x000066 +animation background = solid(color=space_blue) +run background + +# Twinkle stars on top +animation stars = twinkle_animation( + color=0xFFFFAA # Light yellow sparkles + density=8 # density (moderate sparkles) + twinkle_speed=100ms # twinkle speed + fade_speed=50 # when no unit, time unit is 'ms' + priority=8 # default priority is 10, so being lower puts it on top +) +run stars +``` + +Here's what happens: + +1. The `background` animation runs with default priority 10 +2. The `stars` animation runs with priority 8 +3. Since 8 < 10, stars render "on top" of the background +4. Where there's no star, the blue background shows through + +This layering technique is fundamental - you'll use it throughout this tutorial to create complex scenes. + +--- + +## Chapter 2: Color Cycling with Palettes + +So far, our colors have been static. This chapter introduces **dynamic colors** that change over time, creating smooth color transitions without any manual timing code. + +The key concept is the **color provider** - instead of a fixed color value, you use a function that produces different colors as time passes. + +### 2.1 Built-in Palette Cycling + +Palette Rotation + +The simplest way to create changing colors is with `color_cycle`, which steps through a palette of colors over time. + +```berry +# Rainbow colors cycling through the entire strip over time + +# Define a color that cycles over time, cycle is 5 seconds +# PALETTE_RAINBOW_W defines 7 rainbow colors + white +color rainbow_color = color_cycle(colors=PALETTE_RAINBOW_W, period=5s) +animation back = solid(color=rainbow_color) +run back +``` + +Notice the difference from Chapter 1: + +- Before: `color=red` (static color) +- Now: `color=rainbow_color` (dynamic color provider) + +The `color_cycle` function creates a color provider that cycles through the palette. The `period=5s` means one complete cycle takes 5 seconds. + +**Built-in palettes:** + +- `PALETTE_RAINBOW` - 7 rainbow colors +- `PALETTE_RAINBOW_W` - 7 rainbow colors + white +- `PALETTE_RAINBOW2` - Rainbow with first color repeated at end for smooth roll-over +- `PALETTE_RAINBOW_W2` - Rainbow + white with first color repeated at end for smooth roll-over +- `PALETTE_FIRE` - Fire colors (red, orange, yellow) + +### 2.2 Custom Palettes + +Custom Palette + +Built-in palettes are convenient, but you'll often want your own color schemes. Define custom palettes as arrays of hex colors. + +```berry +# Rainbow colors cycling with custom palette + +# Define a palette of rainbow colors including white +palette rainbow_with_white = [ + 0xFC0000 # Red + 0xFF8000 # Orange + 0xFFFF00 # Yellow + 0x00FF00 # Green + 0x00FFFF # Cyan + 0x0080FF # Blue + 0x8000FF # Violet + 0xCCCCCC # White +] + +# Define a color that cycles over time +color rainbow_color = color_cycle(colors=rainbow_with_white, period=5s) + +# Use it in a solid animation +animation back = solid(color=rainbow_color) +run back +``` + +The `palette` keyword creates a named color collection. Colors are listed in order - the cycle will go Red → Orange → Yellow → ... → White → Red. + +--- + +## Chapter 3: Smooth Color Transitions + +Chapter 2 showed `color_cycle`, which steps discretely between colors. This chapter introduces `rich_palette`, which creates **smooth, interpolated transitions** - the color gradually morphs from one to the next. + +### 3.1 Rich Palette Animation + +Rich Palette + +The `rich_palette_animation` is a complete animation that handles both the color transitions and rendering. It's the easiest way to get smooth rainbow effects. + +```berry +# Smooth cycling through rainbow colors + +animation back = rich_palette_animation() +# Equivalent to: +# animation back = rich_palette_animation(colors=PALETTE_RAINBOW, period=5s, +# transition_type=SINE, brightness=100%) +run back +``` + +With no parameters, it uses sensible defaults. The `transition_type=SINE` creates smooth, natural-looking transitions that ease in and out. + +### 3.2 Rich Palette with Custom Colors + +Rich Palette Custom + +For more control, use `rich_palette` as a **color provider** (not an animation). This lets you use smooth color transitions with any animation type. + +```berry +# Smooth cycling through rainbow colors with custom palette + +palette rainbow_with_white = [ + 0xFC0000 # Red + 0xFF8000 # Orange + 0xFFFF00 # Yellow + 0x00FF00 # Green + 0x00FFFF # Cyan + 0x0080FF # Blue + 0x8000FF # Violet + 0xCCCCCC # White + 0xFC0000 # Red - add first color at end for smooth roll-over +] + +# Define a color that cycles over time with smooth transitions +color rainbow_color_rollover = rich_palette(period=10s) + +# Use the dynamic color in a solid animation +animation back = solid(color=rainbow_color_rollover) +run back +``` + +!!! tip "Smooth Roll-over" + Add the first color at the end of your palette to ensure smooth transitions when the cycle repeats. + + +--- + +## Chapter 4: Spatial Patterns and Gradients + +Until now, all LEDs displayed the same color at any given moment. This chapter introduces **spatial variation** - different LEDs showing different colors simultaneously, creating gradients and patterns across the strip. + +The key insight is that color providers can work in two dimensions: + +- **Time**: Colors change as time passes (what we've done so far) +- **Space**: Colors vary by position along the strip (new in this chapter) + +### 4.1 Rainbow Gradient + +Color Pattern + +A gradient maps colors to positions along the strip. The `palette_gradient_animation` does exactly this. + +```berry +# Rainbow pattern across the strip + +# Define a palette with period=0 (no time-based change, only spatial) +color rainbow_rich_color = rich_palette(colors=PALETTE_RAINBOW_W, period=0) + +# Create a gradient across the whole strip +animation back_pattern = palette_gradient_animation(color_source = rainbow_rich_color) +run back_pattern +``` + +The magic is `period=0` - this tells the color provider to ignore time and only vary by position. The gradient animation then maps the palette across the strip's length. + +### 4.2 Multiple Gradient Repetitions + +Gradient 2x + +By default, the gradient spans the entire strip once. Use `spatial_period` to control how many LEDs one complete gradient cycle covers. + +```berry +# Rainbow gradient with 2 repetitions across the strip + +color rainbow_rich_color = rich_palette(colors=PALETTE_RAINBOW_W, period=0) + +# Get the strip length as a variable +set strip_len = strip_length() + +# Create gradient with half the strip length as spatial period +animation back_pattern = palette_gradient_animation( + color_source = rainbow_rich_color + spatial_period = strip_len / 2 +) +run back_pattern +``` + +With `spatial_period = strip_len / 2`, the gradient repeats twice across the strip. Use smaller values for more repetitions. + +!!! note "Using strip_length()" + The `strip_length()` function returns the current LED count. Since it's a value provider, you must assign it to a variable with `set` before using it in calculations. + +### 4.3 Oscillating Spatial Period + +Oscillating Period + +Here's where things get interesting: you can make **any parameter dynamic** by using a value provider instead of a fixed number. This example makes the gradient "breathe" by oscillating its spatial period. + +```berry +# Rainbow gradient with oscillating spatial period + +color rainbow_rich_color = rich_palette(colors=PALETTE_RAINBOW_W, period=0) + +set strip_len = strip_length() + +# Oscillate spatial period between 1/2 and 3/2 of strip length +set period = sine_osc(min_value = (strip_len - 1) / 2, max_value = (3 * strip_len) / 2, duration = 5s) + +animation back = palette_gradient_animation(color_source = rainbow_rich_color, spatial_period = period) +run back +``` + +The `sine_osc` function creates a **value provider** - a number that changes smoothly over time. Here it oscillates the spatial period, making the gradient compress and expand. + +**Available oscillators:** + +| Oscillator | Description | +|------------|-------------| +| `sine_osc` | Smooth sine wave | +| `cosine_osc` | Cosine wave (same as sine, different phase) | +| `triangle` | Linear up and down | +| `sawtooth` | Linear up, instant reset | +| `smooth` | Smooth cosine-based | +| `square` | Instant on/off | + +### 4.4 Rotating Gradient + +Rotating Gradient + +Make the gradient rotate along the strip: + +```berry +# Rainbow gradient rotating along the strip over 5 seconds + +color rainbow_rich_color = rich_palette(colors=PALETTE_RAINBOW_W, period=0) + +animation back = palette_gradient_animation( + color_source = rainbow_rich_color + shift_period = 5s # Complete rotation in 5 seconds +) +run back +``` + +The `shift_period` parameter makes the entire pattern shift along the strip. + +### 4.5 VU-Meter Style Animation + +VU Meter + +Create a meter/bar that fills based on a value: + +```berry +# VU-meter style animation with green-yellow-red gradient + +# Define a VU-meter palette with position-based colors +palette vue_meter_palette = [ + ( 0, 0x00FF00) # Green at 0% + (143, 0x00FF00) # Green until ~56% + + (164, 0xFFFF00) # Yellow transition + (207, 0xFFFF00) # Yellow until ~81% + + (228, 0xFF0000) # Red transition + (255, 0xFF0000) # Red at 100% +] + +color rainbow_rich_color = rich_palette(colors=vue_meter_palette, period=0, transition_type=LINEAR) + +# Sawtooth value from 0% to 100% +set level = sawtooth(min_value = 0%, max_value=100%, duration = 2s) + +# Create the meter animation +animation back = palette_meter_animation(color_source = rainbow_rich_color, level = level) +run back +``` + +The palette uses position-based entries `(position, color)` where position ranges from 0 to 255. + +### 4.6 Custom Value Functions + +Random Meter + +Sometimes the built-in oscillators aren't enough - you need custom logic like random values, sensor readings, or complex calculations. The DSL lets you embed native Berry code and use it in your animations. + +**How it works:** + +1. **Embed Berry code** using `berry """..."""` blocks (triple quotes) and multi-line content +2. **Declare the function** with `extern function name` so the DSL knows about it +3. **Call the function** in your animation parameters + +**Function signature:** Your function receives an `engine` parameter that provides access to timing and animation state. It should return a value (typically 0-255 for levels/opacity). + +```berry +# VU-meter with random level using custom Berry function + +# Step 1: Embed native Berry code +# The function receives 'engine' which has properties like time_ms +berry """ +def rand_meter(engine) + # Use time to generate pseudo-random values + # The & 0xFF ensures result is 0-255 + return (engine.time_ms * 2654435761) & 0xFF +end +""" + +# Step 2: Declare the function for DSL use +# This tells the transpiler that 'rand_meter' is a valid function +extern function rand_meter + +palette vue_meter_palette = [ + ( 0, 0x00FF00) # Green + (143, 0x00FF00) + (164, 0xFFFF00) # Yellow + (207, 0xFFFF00) + (228, 0xFF0000) # Red + (255, 0xFF0000) +] + +color rainbow_rich_color = rich_palette(colors=vue_meter_palette, period=0, transition_type=LINEAR) + +# Step 3: Use the custom function as a parameter +# Call it with () - the engine parameter is passed automatically +animation back = palette_meter_animation(color_source = rainbow_rich_color, level = rand_meter()) +run back +``` + +The `berry """..."""` block lets you embed arbitrary Berry code. The `extern function` declaration makes it available to the DSL. + +--- + +## Chapter 5: Beacons and Moving Effects + +Previous chapters filled the entire strip with colors or gradients. This chapter introduces **beacons** - localized highlights at specific positions. By animating the position, you create moving effects like the classic "Cylon eye" scanner. + +A beacon has five key properties: + +| Property | Description | Default | +|----------|-------------|---------| +| `color` | The highlight color | `white` | +| `back_color` | The background color | `transparent` | +| `pos` | Position on the strip (pixel index) | `0` | +| `beacon_size` | Width of the highlight in pixels | `1` | +| `slew_size` | Fade-out width on each edge (soft edges) | `0` | + +### 5.1 Static Beacon + +Static Beacon + +Let's start with a stationary beacon - a red highlight on a blue background. + +```berry +# Static beacon + +animation back = beacon_animation( + back_color = blue + color = red + pos = 5 # Start at pixel 5 + beacon_size = 7 # 7 pixels wide +) +run back +``` + +### 5.2 Beacon with Slew (Soft Edges) + +Beacon Slew + +Hard edges can look harsh. The `slew_size` parameter adds a gradual fade on each side of the beacon, creating softer, more natural-looking highlights. + +**How slew works:** + +- The slew creates a transition zone of `slew_size` pixels on each side of the beacon +- Within the slew zone, the color gradually blends from `color` to `back_color` +- The blending is linear: the first slew pixel is mostly `color`, the last is mostly `back_color` +- If `back_color` is `transparent`, the edges become progressively transparent, allowing underlying animations to show through + +**Total width:** A beacon with `beacon_size=7` and `slew_size=3` occupies 7 + 3 + 3 = 13 pixels total (7 solid + 3 fade on each side). + +```berry +# Static beacon with slew + +animation back = beacon_animation( + back_color = blue + color = red + pos = 5 + beacon_size = 7 + slew_size = 3 # 3 pixel fade on each side +) +run back +``` + +### 5.3 Animated Slew + +Oscillating Slew + +Remember: any numeric parameter can be replaced with a value provider. Here we make the slew size pulse in and out. + +```berry +# Beacon with oscillating slew + +set slew = cosine_osc(min_value = 0, max_value = 4, duration = 2s) + +animation back = beacon_animation( + back_color = blue + color = red + pos = 5 + beacon_size = 7 + slew_size = slew # Dynamic slew size +) +run back +``` + +### 5.4 Cylon Eye (Moving Beacon) + +Cylon Eye + +Now for the classic effect: a beacon that moves back and forth across the strip. We use `cosine_osc` on the `pos` parameter to create smooth scanning motion. + +```berry +# Moving red beacon - Cylon style + +set strip_len = strip_length() + +animation back = beacon_animation( + color = red + pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 5s) + beacon_size = 3 # small 3 pixels eye + slew_size = 2 # with 2 pixel shading around +) +run back +``` + +The cosine oscillator creates smooth acceleration and deceleration at the ends, just like the original Battlestar Galactica Cylon! + +### 5.5 Rainbow Cylon with Stars + +Rainbow Cylon + +Let's combine everything we've learned: layered animations, dynamic colors, and moving beacons. This example creates a scene with two layers. + +**Layer structure (rendered bottom to top):** + +| Priority | Animation | Description | +|----------|-----------|-------------| +| 10 (default) | `stars` | Twinkling background - rendered first | +| 5 | `back` | Moving rainbow beacon - rendered on top | + +**How layering works:** + +1. The `stars` animation runs with default priority 10 +2. The `back` beacon runs with priority 5 (lower = on top) +3. The beacon uses `back_color = transparent` (default), so where there's no beacon, the stars show through +4. The beacon's slew creates a soft edge that gradually reveals the stars underneath + +```berry +# Moving rainbow beacon with twinkling stars background + +set strip_len = strip_length() + +# Twinkling stars background +animation stars = twinkle_animation( + color=0xFFFFAA + density=2 + twinkle_speed=100ms + fade_speed=100 + # priority = 10 (default) +) +run stars + +# Moving beacon with dynamic color +# back_color defaults to transparent, so stars show through +animation back = beacon_animation( + color = rich_palette(colors=PALETTE_RAINBOW_W2, period=5s) + pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 5s) + beacon_size = 3 + slew_size = 2 + priority = 5 # Lower priority = rendered on top of stars +) +run back +``` + +The result: a rainbow-colored eye scans across a field of twinkling stars, with the stars visible everywhere except where the beacon is. + +### 5.6 Beacon as Opacity Mask + +Opacity Mask + +Instead of layering animations with priority, you can use one animation as an **opacity mask** for another. This creates a "window" effect where the mask controls what's visible. + +**How opacity masks work:** + +1. The mask animation renders to determine opacity values (0-255 per pixel) +2. Where the mask is bright (white/high values), the main animation is fully visible +3. Where the mask is dark (black/low values), the main animation is transparent +4. The mask's actual color doesn't matter - only its brightness/alpha is used + +**In this example:** + +- `moving_eye` is a beacon that moves back and forth +- `eye_pattern` is a red-blue-red gradient across the entire strip +- The gradient is only visible where the beacon is - creating a "spotlight" effect +- The beacon's slew creates soft edges on the spotlight + +```berry +# Moving beacon used as opacity filter on pattern + +set strip_len = strip_length() + +# Define a red-blue-red gradient palette +palette red_blue_red_palette = [ red, 0x3333FF, red ] +color red_blue_red_color = rich_palette(colors=red_blue_red_palette) + +# Moving beacon as opacity mask +# The color is white but it doesn't matter - only brightness counts +animation moving_eye = beacon_animation( + color = white # Color doesn't matter, only brightness/alpha + pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 5s) + beacon_size = 3 + slew_size = 2 +) + +# Apply the mask to a gradient +# The gradient exists everywhere, but only shows where the beacon is +animation eye_pattern = palette_gradient_animation( + color_source = red_blue_red_color + opacity = moving_eye # Use beacon as opacity mask +) +run eye_pattern +``` + +The result: a moving "window" reveals different parts of the underlying gradient as it scans across the strip. + + +--- + +## Chapter 6: Shutters and Sequences + +This chapter introduces two powerful concepts: + +1. **Shutters** - Expanding/contracting effects created by animating a beacon's size +2. **Sequences** - Orchestrating multiple animations with precise timing control + +Sequences are essential when you need things to happen in order: play animation A, then wait, then play animation B, then change a color, etc. + +### 6.1 Simple Shutter + +Simple Shutter + +A shutter effect is simply a beacon with an animated size. Using `sawtooth` makes it grow linearly then reset. + +```berry +# Shutter left to right using beacon + +set strip_len = strip_length() + +# Sawtooth from 0 to strip_len - grows linearly, then resets +set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = 1.5s) + +animation shutter_lr_animation = beacon_animation( + color = red + back_color = blue + pos = 0 # Start from left + beacon_size = shutter_size # Expanding size +) +run shutter_lr_animation +``` + +### 6.2 Shutter with Rotating Colors + +Rotating Shutter + +Now let's add color changes after each shutter cycle. This requires a **sequence** - a way to run code at specific times. + +A sequence contains steps that execute in order: + +- `play animation for duration` - Run an animation for a specific time +- `wait duration` - Pause before the next step +- `restart provider` - Reset a value provider's timing +- `repeat N times { ... }` - Loop a block of steps + +```berry +# Shutter with rotating colors using sequence + +set strip_len = strip_length() +set period = 1.5s + +set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period) + +# Two color providers cycling through rainbow +# period=0 means they don't auto-cycle - we control them manually +color col1 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +color col2 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +col2.next = 1 # Shift col2 by one color at startup + +animation shutter_lr_animation = beacon_animation( + color = col2 + back_color = col1 + pos = 0 + beacon_size = shutter_size +) + +# Sequence running forever +sequence shutter_seq repeat forever { + restart shutter_size # Sync timing + play shutter_lr_animation for period + col1.next = 1 # Advance col1 to next color in palette + col2.next = 1 # Advance col2 to next color in palette +} +run shutter_seq +``` + +**Understanding `color_cycle` with `period=0`:** + +When `period=0`, the color provider doesn't automatically cycle through colors over time. Instead, you control it manually by writing to its `next` property: + +- `col.next = 1` - Advance to the next color in the palette +- `col.next = 2` - Skip ahead by 2 colors +- `col.next = -1` - Go back to the previous color + +This gives you precise control over color changes, synchronized with your sequence timing. + +**Sequence statements:** + +| Statement | Description | +|-----------|-------------| +| `play animation for duration` | Play an animation for a specific time | +| `wait duration` | Pause the sequence | +| `restart value_provider` | Reset a value provider's timing | +| `repeat N times { ... }` | Repeat a block N times | +| `repeat forever { ... }` | Repeat indefinitely | + +### 6.3 Central Shutter + +Central Shutter + +Instead of expanding from the left edge, this shutter expands from the center outward. + +**From left-right to center-out:** + +The key difference is the `pos` calculation: + +- **Left-right shutter** (6.1): `pos = 0` - beacon starts at left edge and grows rightward +- **Center-out shutter** (6.3): `pos = strip_len_2 - (shutter_size + 1) / 2` - beacon stays centered as it grows + +The formula `strip_len_2 - (shutter_size + 1) / 2` keeps the beacon centered by: + +1. Starting at the center (`strip_len_2`) +2. Subtracting half the beacon size so it expands equally in both directions +3. As `shutter_size` grows from 0 to `strip_len`, the position moves left to keep the beacon centered + +```berry +# Shutter central with rotating colors + +set strip_len = strip_length() +set strip_len_2 = (strip_len + 1) / 2 # Half length (center position) + +set period = 1.5s +set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period) + +color col1 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +color col2 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +col2.next = 1 + +# Position calculated to center the beacon as it grows +animation shutter_inout_animation = beacon_animation( + color = col2 + back_color = col1 + pos = strip_len_2 - (shutter_size + 1) / 2 + beacon_size = shutter_size +) + +sequence shutter_seq repeat forever { + restart shutter_size + play shutter_inout_animation for period + col1.next = 1 + col2.next = 1 +} +run shutter_seq +``` + +### 6.4 Bidirectional Shutter + +Bidirectional Shutter + +This example combines both directions: first the shutter expands from center (in-out), then contracts back to center (out-in). The sequence cycles through all palette colors in each direction before switching. + +**Sequence structure:** + +The sequence contains two nested `repeat` blocks: + +1. **First repeat**: Runs the in-out animation once for each color in the palette +2. **Second repeat**: Runs the out-in animation once for each color in the palette + +The repeat count `col1.palette_size` dynamically gets the number of colors in the palette (8 for `PALETTE_RAINBOW_W`), so the animation automatically adapts if you change palettes. + +**Why `restart` is important:** + +The `shutter_size` sawtooth oscillator runs continuously based on elapsed time. Without `restart`, each iteration would start wherever the sawtooth happened to be, causing visual glitches. By calling `restart shutter_size` at the beginning of each iteration: + +- The sawtooth resets to its starting value (0) +- The shutter animation starts cleanly from the beginning +- Everything stays perfectly synchronized with the sequence timing + +```berry +# Shutter central in-out and out-in with rotating colors + +set strip_len = strip_length() +set strip_len_2 = (strip_len + 1) / 2 + +set period = 1.5s +set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period) + +color col1 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +color col2 = color_cycle(colors=PALETTE_RAINBOW_W, period=0) +col2.next = 1 + +# In-out animation: beacon grows from center outward +animation shutter_inout_animation = beacon_animation( + color = col2 + back_color = col1 + pos = strip_len_2 - (shutter_size + 1) / 2 + beacon_size = shutter_size +) + +# Out-in animation: beacon shrinks from edges toward center +# Uses inverted size (strip_len - shutter_size) and swapped colors +animation shutter_outin_animation = beacon_animation( + color = col1 + back_color = col2 + pos = strip_len_2 - (strip_len - shutter_size + 1) / 2 + beacon_size = strip_len - shutter_size +) + +sequence shutter_seq repeat forever { + # First: cycle through all colors in-out (8 iterations for PALETTE_RAINBOW_W) + repeat col1.palette_size times { + restart shutter_size # Reset sawtooth to 0 for clean start + play shutter_inout_animation for period + col1.next = 1 # Advance both colors + col2.next = 1 + } + # Then: cycle through all colors out-in (8 more iterations) + repeat col1.palette_size times { + restart shutter_size # Reset sawtooth again + play shutter_outin_animation for period + col1.next = 1 + col2.next = 1 + } +} +run shutter_seq +``` + +--- + +## Chapter 7: Crenel Patterns + +Crenels create repeating square wave patterns - alternating blocks of two colors, like the battlements on a castle wall. They're useful for: + +- Creating striped patterns +- Building opacity masks with regular gaps +- Marquee-style effects when animated + +### 7.1 Static Crenel + +Static Crenel + +A basic crenel alternates between two colors with configurable block sizes. + +**Crenel parameters:** + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `color` | Color of the "high" pulses | `white` | +| `back_color` | Color of the "low" gaps | `transparent` (0x00000000) | +| `pos` | Starting position on the strip | `0` | +| `pulse_size` | Width of each pulse in pixels | `1` | +| `low_size` | Width of each gap in pixels | `3` | +| `nb_pulse` | Number of pulses (`-1` = infinite) | `-1` | + +The pattern repeats with a period of `pulse_size + low_size` pixels. + +```berry +# Static crenel pattern + +animation back = crenel_animation( + color = red + back_color = blue + pulse_size = 2 # 2 pixels of 'color' + low_size = 2 # 2 pixels of 'back_color' +) +run back +``` + +This creates a pattern: 🔴🔴🔵🔵🔴🔴🔵🔵... across the entire strip. + +### 7.2 Variable Number of Pulses + +Variable Pulses + +Instead of showing all pulses (`nb_pulse = -1`), you can animate the number of visible pulses using a value provider. The `triangle` oscillator smoothly varies `nb_pulse` from 0 (no pulses) to the maximum that fits on the strip. + +To compute the maximum number of pulses, use the formula: `max_pulses = (strip_len + period - 1) / period` where `period = pulse_size + low_size`. This ceiling division ensures we count partial pulses at the end of the strip. + +```berry +# Crenel with variable number of pulses + +set strip_len = strip_length() +set period = 4 # pulse_size (2) + low_size (2) +set max_pulses = (strip_len + period - 1) / period + +set nb_pulse = triangle(min_value = 0, max_value = max_pulses, duration = 2s) + +animation back = crenel_animation( + color = red + back_color = blue + pulse_size = 2 + low_size = 2 + nb_pulse = nb_pulse +) +run back +``` + +### 7.3 Variable Pulse Size + +Variable Size + +Instead of a fixed `pulse_size`, you can use a value provider to animate the pulse width over time. Here, a `triangle` oscillator smoothly varies the pulse size between 0 and 4 pixels over 2 seconds, creating a breathing effect on the crenel pattern: + +```berry +# Crenel with variable pulse size + +set pulse_size = triangle(min_value = 0, max_value = 4, duration = 2s) + +animation back = crenel_animation( + color = red + back_color = blue + pulse_size = pulse_size + low_size = 2 +) +run back +``` + +### 7.4 Dynamic Colors + +Dynamic Colors + +The `color` parameter also accepts a color provider instead of a static color. This example uses `rich_palette` to cycle through rainbow colors over 5 seconds, making the crenel pulses continuously change color while the blue background remains fixed: + +```berry +# Crenel with dynamic color + +color rainbow_color = rich_palette(colors=PALETTE_RAINBOW_W2, period=5s) + +animation back = crenel_animation( + color = rainbow_color + back_color = blue + pulse_size = 2 + low_size = 2 +) +run back +``` + +### 7.5 Crenel as Opacity Mask + +Crenel Mask + +Instead of using a crenel directly as a visible animation, you can use it as an **opacity mask** for another animation. This creates a "window" effect where the crenel pattern controls what's visible. + +**How opacity masks work:** + +1. The mask animation renders to determine opacity values (0-255 per pixel) +2. Where the mask is bright (white/high values), the main animation is fully visible +3. Where the mask is dark or transparent, the main animation is hidden +4. The mask's actual color doesn't matter - only its brightness/alpha is used + +**In this example:** + +- `back` is a solid blue background at priority 20 +- `mask` is a crenel pattern with white pulses and transparent gaps +- `pattern` is a rotating rainbow gradient that uses the crenel as its `opacity` parameter +- The rainbow gradient is only visible where the crenel pulses are white - creating alternating "windows" of rainbow color on a blue background + +```berry +# Crenel used as opacity mask + +# Blue background +animation back = solid(color = blue, priority = 20) +run back + +# Crenel mask (white = visible, transparent = hidden) +animation mask = crenel_animation( + color = white + back_color = transparent + pulse_size = 2 + low_size = 2 +) + +# Rainbow gradient masked by crenel +color rainbow_rich_color = rich_palette(colors=PALETTE_RAINBOW_W, period=0) +animation pattern = palette_gradient_animation( + color_source = rainbow_rich_color + shift_period = 2s # Rotating gradient + opacity = mask # Apply crenel mask +) +run pattern +``` + + +--- + +## Chapter 8: Templates for Reusable Animations + +As your animations grow more complex, you'll want to reuse patterns with different parameters. **Templates** solve this by letting you define animation "blueprints" with configurable parameters. + +Think of templates like functions in programming: define once, use many times with different inputs. + +### 8.1 Simple Template: Cylon Eye + +Template Cylon + +The `template animation` keyword creates a new animation type that can be instantiated just like built-in animations (`solid`, `beacon_animation`, etc.). Once defined, you use it by calling `animation my_anim = template_name(param1=value1, ...)` - exactly like native animations. + +**Defining parameters:** + +Each `param` declaration creates a configurable input for your template: + +```berry +param parameter_name type type_name default default_value +``` + +- `parameter_name`: The name you'll use inside the template and when instantiating +- `type type_name`: Optional type constraint (see table below) +- `default value`: Optional default value if not provided at instantiation + +**Available parameter types:** + +| Type | Description | Example | +|------|-------------|---------| +| `color` | Color value (hex or named) | `param fg type color default red` | +| `palette` | Palette definition (bytes) | `param colors type palette` | +| `time` | Time value with unit (converted to ms) | `param period type time default 5s` | +| `int` | Integer value | `param count type int default 10` | +| `bool` | Boolean (true/false) | `param enabled type bool default true` | +| `string` | String value | `param name type string default "anim"` | +| `percentage` | Percentage (0-255) | `param brightness type percentage default 255` | +| `number` | Generic numeric | `param value type number` | +| `any` | Any type (no validation) | `param data type any` | + +Let's package the Cylon eye effect from Chapter 5 as a reusable template. Users can customize the color, speed, and priority without rewriting the animation logic: + +```berry +# Template for Cylon-style scanning eye + +template animation cylon_eye { + param eye_color type color default red + param back_color type color default transparent + param period type time default 5s + param priority default 5 + + set strip_len = strip_length() + + animation eye_animation = beacon_animation( + color = eye_color + back_color = back_color + pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = period) + beacon_size = 3 + slew_size = 2 + priority = priority + ) + + run eye_animation +} + +# Use the template with defaults +animation eye = cylon_eye() +run eye +``` + +### 8.2 Template with Palette Parameter + +Template Color Cycle + +Templates can accept complex types like palettes. Here we create a reusable color cycling animation where the user provides their own palette and cycle period: + +```berry +# Template for color cycling animation + +template animation color_cycle2 { + param colors type palette + param period default 5s + + color rainbow_color = color_cycle(colors=colors, period=period) + animation back = solid(color=rainbow_color) + run back +} + +# Define a custom palette +palette rgb = [ + 0xFC0000 # Red + 0x00FF00 # Green + 0x0080FF # Blue +] + +# Use the template with custom parameters +animation main = color_cycle2(colors = rgb, period = 2s) +run main +``` + +### 8.3 Advanced Template with Conditional Flags + +Template Shutter + +Templates support `bool` parameters that can be used with `if` statements inside sequences. This allows users to enable or disable parts of the animation at instantiation time. Here we create a bidirectional shutter that can optionally run in-out, out-in, or both directions. + +**Dynamic parameter changes:** + +You can also modify template parameters at runtime using Berry code. Note that DSL variable names get an underscore suffix in Berry to avoid collisions with reserved words (e.g., `main` becomes `main_`). For example: `main_.inout = false` disables the in-out animation while it's running. + +```berry +# Template with conditional flags for bidirectional shutter + +template animation shutter_bidir { + param colors type palette + param period default 2s + param inout type bool default true # Enable in-out animation + param outin type bool default true # Enable out-in animation + + set strip_len = strip_length() + set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period) + + # Two rotating color providers + color col1 = color_cycle(colors=colors, period=0) + color col2 = color_cycle(colors=colors, period=0) + col2.next = 1 + + # In-out shutter + animation shutter_inout_animation = beacon_animation( + color = col2 + back_color = col1 + pos = 0 + beacon_size = shutter_size + slew_size = 0 + priority = 5 + ) + + # Out-in shutter + animation shutter_outin_animation = beacon_animation( + color = col1 + back_color = col2 + pos = 0 + beacon_size = strip_len - shutter_size + slew_size = 0 + priority = 5 + ) + + # Sequence with conditional blocks + sequence shutter_seq repeat forever { + if inout { # Only if inout is true + repeat col1.palette_size times { + restart shutter_size + play shutter_inout_animation for period + col1.next = 1 + col2.next = 1 + } + } + if outin { # Only if outin is true + repeat col1.palette_size times { + restart shutter_size + play shutter_outin_animation for period + col1.next = 1 + col2.next = 1 + } + } + } + run shutter_seq +} + +# Define palette +palette rainbow_with_white = [ + 0xFC0000 # Red + 0xFF8000 # Orange + 0xFFFF00 # Yellow + 0x00FF00 # Green + 0x00FFFF # Cyan + 0x0080FF # Blue + 0x8000FF # Violet + 0xCCCCCC # White +] + +# Use the template +animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s) +run main +``` + +## Quick Reference Card + +### Animation Types + +| Animation | Description | Key Parameters | +|-----------|-------------|----------------| +| `solid` | Solid color fill | `color` | +| `twinkle_animation` | Twinkling stars effect | `color`, `density`, `twinkle_speed`, `fade_speed` | +| `beacon_animation` | Positioned pulse/highlight | `color`, `pos`, `beacon_size`, `slew_size` | +| `crenel_animation` | Square wave pattern | `color`, `back_color`, `pulse_size`, `low_size` | +| `rich_palette_animation` | Smooth palette cycling | `colors`, `period`, `transition_type` | +| `palette_gradient_animation` | Gradient across strip | `color_source`, `spatial_period`, `shift_period` | +| `palette_meter_animation` | VU-meter style bar | `color_source`, `level` | +| `breathe_animation` | Breathing/pulsing effect | `color`, `period` | +| `comet_animation` | Moving comet with tail | `color`, `tail_length`, `speed` | + +### Colors + +```berry +# Predefined +red, green, blue, white, yellow, orange, purple, cyan, transparent + +# Custom hex +color my_color = 0xRRGGBB +``` + +### Palettes + +```berry +# Built-in +PALETTE_RAINBOW, PALETTE_RAINBOW_W, PALETTE_FIRE + +# Custom +palette my_palette = [ 0xFF0000, 0x00FF00, 0x0000FF ] + +# Position-based +palette gradient = [ (0, red), (128, yellow), (255, green) ] +``` + +### Value Providers + +```berry +sine_osc(min_value=0, max_value=255, duration=2s) +cosine_osc(min_value=0, max_value=255, duration=2s) +triangle(min_value=0, max_value=255, duration=2s) +sawtooth(min_value=0, max_value=255, duration=2s) +smooth(min_value=0, max_value=255, duration=2s) +square(min_value=0, max_value=255, duration=2s) +strip_length() +``` + +### Time Units + +```berry +500ms # milliseconds +2s # seconds +1m # minutes +``` + +### Percentages + +```berry +0% # 0 +50% # 128 +100% # 255 +``` + +### Sequence Statements + +```berry +play animation for 5s +wait 1s +restart value_provider +repeat 3 times { ... } +repeat forever { ... } +if condition { ... } +``` + +### Template Definition + +```berry +template animation name { + param param_name type type_name default value + # ... body ... +} +``` + +### Resources + +- **[DSL Reference](Dsl_Reference.md)** - Complete syntax documentation +- **[Animation Classes](Animation_Class_Hierarchy.md)** - All available animations +- **[Oscillation Patterns](Oscillation_Patterns.md)** - Value provider waveforms +- **[Examples](Examples.md)** - More animation examples +- **[Online Emulator](https://tasmota.github.io/docs/Tasmota-Berry-emulator/index.html)** - Test animations in your browser diff --git a/lib/libesp32/berry_animation/animation_docs/Dsl_Reference.md b/lib/libesp32/berry_animation/animation_docs/Dsl_Reference.md new file mode 100644 index 000000000..7430dd49f --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Dsl_Reference.md @@ -0,0 +1,1695 @@ +# 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. + +For detailed information about the DSL transpiler's internal architecture and processing flow, see [Transpiler_Architecture.md](Transpiler_Architecture.md). + +## 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: + +```berry +# This is a full-line comment +# strip length 30 # This is an inline comment (TEMPORARILY DISABLED) +color bordeaux = 0x6F2C4F # 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: + +```berry +# Strip configuration is handled automatically +# strip length 60 # TEMPORARILY DISABLED + +# Color definitions +color bordeaux = 0x6F2C4F +color majorelle = 0x6050DC + +# Animation definitions +animation pulse_bordeaux = pulsating_animation(color=bordeaux, period=2s) + +# Property assignments +pulse_red.priority = 10 + +# Sequences +sequence demo { + play pulse_bordeaux 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 (temporarily disabled, reserved keyword) +- `set` - Variable assignment +- `import` - Import Berry modules +- `berry` - Embed arbitrary Berry code +- `extern` - Declare external Berry functions for DSL use + +**Definition Keywords:** +- `color` - Color definition +- `palette` - Palette definition +- `animation` - Animation definition +- `sequence` - Sequence definition +- `template` - Template definition +- `param` - Template parameter declaration +- `type` - Parameter type annotation + +**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 +- `restart` - Restart value provider or animation from beginning + +**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 + +```berry +42 # Integer +3.14 # Floating point +-5 # Negative number +``` + +### Time Values + +Time values require a unit suffix and are automatically converted to milliseconds: + +```berry +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: + +```berry +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 + +```berry +0xFF0000 # Red (RGB format) +0x80FF0000 # Semi-transparent red (ARGB format) +``` + +#### Named Colors + +```berry +red # Predefined color name +blue # Predefined color name +transparent # Transparent color +``` + +### Strings + +```berry +"hello" # Double-quoted string +'world' # Single-quoted string +``` + +### Identifiers + +Identifiers must start with a letter or underscore, followed by letters, digits, or underscores: + +```berry +my_color # Valid identifier +_private_var # Valid identifier +Color123 # Valid identifier +``` + +## Configuration Statements + +### Strip Configuration + +**Note: The `strip` directive is temporarily disabled.** Strip configuration is handled automatically by the host system. + +~~The `strip` statement configures the LED strip and must be the first statement if present:~~ + +```berry +# strip length 60 # TEMPORARILY DISABLED +``` + +~~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: + +```berry +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) + +# Computed values using strip length +set strip_len = strip_length() # Get current strip length +``` + +### Import Statements + +The `import` keyword imports Berry modules for use in animations: + +```berry +import user_functions # Import user-defined functions +import my_custom_module # Import custom animation libraries +import math # Import standard Berry modules +import string # Import utility modules +``` + +**Import Behavior:** +- Module names should be valid identifiers (no quotes needed in DSL) +- Import statements are typically placed at the beginning of DSL files +- Transpiles to standard Berry `import "module_name"` statements +- Imported modules become available for the entire animation + +**Common Use Cases:** +```berry +# Import user functions for computed parameters +import user_functions + +animation dynamic = solid(color=blue) +dynamic.opacity = my_custom_function() + +# Import custom animation libraries +import fire_effects + +animation campfire = fire_effects.create_fire(intensity=200) +``` + +**Transpilation Example:** +```berry +# DSL Code +import user_functions + +# Transpiles to Berry Code +import "user_functions" +``` + +### Berry Code Blocks + +The `berry` keyword allows embedding arbitrary Berry code within DSL files using triple-quoted strings: + +```berry +berry """ +import math +var custom_value = math.pi * 2 +print("Custom calculation:", custom_value) +""" + +berry ''' +# Alternative syntax with single quotes +def helper_function(x) + return x * 1.5 +end +''' +``` + +**Berry Code Block Features:** +- Code is copied verbatim to the generated Berry code +- Supports both `"""` and `'''` triple-quote syntax +- Can span multiple lines and include complex Berry syntax +- Variables and functions defined in one block are available in subsequent blocks +- Can interact with DSL-generated objects (e.g., `animation_name_.property = value`) + +**Example with DSL Integration:** +```berry +animation pulse = pulsating_animation(color=red, period=2s) + +berry """ +# Modify animation using Berry code +pulse_.opacity = 200 +pulse_.priority = 10 +print("Animation configured") +""" + +run pulse +``` + +### External Function Declarations + +The `extern` keyword declares Berry functions defined in `berry` code blocks so they can be used in DSL expressions and computed parameters: + +```berry +# Define the function in a berry block +berry """ +def rand_meter(time_ms, self) + import math + var r = math.rand() % 101 + return r +end + +def breathing_effect(base_value, amplitude) + import math + var time_factor = (tasmota.millis() / 1000) % 4 # 4-second cycle + var breath = math.sin(time_factor * math.pi / 2) + return int(base_value + breath * amplitude) +end +""" + +# Declare functions as external so DSL knows about them +extern function rand_meter +extern function breathing_effect + +# Now they can be used in DSL expressions +animation back_pattern = palette_meter_animation(value_func = rand_meter) +animation breathing_light = solid(color=blue) +breathing_light.opacity = breathing_effect + +run back_pattern +``` + +**External Function Features:** +- Functions must be declared with `extern function function_name` after being defined in `berry` blocks +- Can be used with or without parentheses: `rand_meter` or `rand_meter()` +- Automatically receive `engine` as the first parameter when called from DSL +- Can be used in computed parameters and property assignments +- Support the same signature pattern as registered user functions + +**Function Signature Requirements:** +External functions should follow the user function pattern where the first parameter is the engine: + +```berry +berry """ +def my_external_func(engine, param1, param2) + # engine is automatically provided by DSL + # param1, param2 are user-provided parameters + return computed_value +end +""" + +extern function my_external_func + +# Usage in DSL (engine parameter is automatic) +animation test = solid(color=red) +test.opacity = my_external_func # Called as my_external_func(engine) +``` + +**Comparison with User Functions:** +- **External functions**: Defined in `berry` blocks within the same DSL file, declared with `extern function` +- **User functions**: Defined in separate Berry modules, imported with `import`, registered with `animation.register_user_function()` +- Both use the same calling convention and can be used interchangeably in DSL expressions + +## Color Definitions + +The `color` keyword defines static colors or color providers: + +```berry +# Static colors +color bordeaux = 0x6F2C4F # Static hex color +color majorelle = 0x6050DC # 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( + colors=bytes("FFFF0000" "FF00FF00" "FF0000FF") + period=5s +) +color breathing_red = breathe_color( + base_color=red + min_brightness=5% + max_brightness=100% + duration=3s + curve_factor=2 +) +color pulsing_blue = pulsating_color( + base_color=blue + min_brightness=20% + max_brightness=80% + duration=1s +) +``` + +## Palette Definitions + +Palettes define color gradients using position-color pairs and support two encoding formats with flexible syntax: + +### Value-Based Palettes (Recommended) + +Standard palettes use value positions from 0-255: + +```berry +# Traditional syntax with commas +palette fire_colors = [ + (0, 0x000000) # Position 0: Black + (128, 0xFF0000) # Position 128: Red + (255, 0xFFFF00) # Position 255: Yellow +] + +# New syntax without commas (when entries are on separate lines) +palette ocean_palette = [ + (0, navy) # Using named colors + (128, cyan) + (255, green) +] + +# Mixed syntax also works +palette matrix_greens = [ + (0, 0x000000), (64, 0x003300) # Multiple entries on one line + (128, 0x006600) # Single entry on separate line + (192, 0x00AA00) + (255, 0x00FF00) +] +``` + +### Tick-Based Palettes (Advanced) + +Palettes can also use tick counts for timing-based transitions: + +```berry +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**: Only hex values (0xRRGGBB) or predefined color names (red, blue, green, etc.) +- **Custom colors**: Previously defined custom colors are NOT allowed in palettes +- **Dynamic palettes**: For palettes with custom colors, use user functions instead +- Entries are automatically sorted by position +- Comments are preserved +- Automatically converted to efficient VRGB bytes format + +### Palette Color Restrictions + +Palettes have strict color validation to ensure compile-time safety: + +**✅ Allowed:** +```berry +palette valid_colors = [ + (0, 0xFF0000) # Hex colors + (128, red) # Predefined color names + (255, blue) # More predefined colors +] +``` + +**❌ Not Allowed:** +```berry +color custom_red = 0xFF0000 +palette invalid_colors = [ + (0, custom_red) # ERROR: Custom colors not allowed + (128, my_color) # ERROR: Undefined color +] +``` + +**Alternative for Dynamic Palettes:** +For palettes that need custom or computed colors, use user functions: + +```berry +# Define a user function that creates dynamic palettes +def create_custom_palette(engine, base_color, intensity) + # Create palette with custom logic + var palette_data = create_dynamic_palette_bytes(base_color, intensity) + return palette_data +end + +# Register for DSL use +animation.register_user_function("custom_palette", create_custom_palette) +``` + +```berry +# Use in DSL +animation dynamic_anim = rich_palette( + colors=custom_palette(0xFF0000, 200) + period=3s +) +``` + +## Animation Definitions + +The `animation` keyword defines instances of animation classes (subclasses of Animation): + +```berry +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: + +```berry +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) + +# Computed properties using arithmetic expressions +set strip_len = strip_length() +pulse_red.position = strip_len / 2 # Center position +pulse_red.opacity = strip_len * 4 # Scale with strip size + +# Animation opacity (using another animation as opacity mask) +animation opacity_mask = pulsating_animation(period=2s) +pulse_red.opacity = opacity_mask # Dynamic opacity from animation +``` + +**Common Properties:** +- `priority` - Animation priority (higher numbers have precedence) +- `opacity` - Opacity level (number, value provider, or animation) +- `position` - Position on strip +- `speed` - Speed multiplier +- `phase` - Phase offset + +## Computed Values + +The DSL supports computed values using arithmetic expressions with value providers and mathematical functions: + +### Safe Patterns + +```berry +# ✅ RECOMMENDED: Single value provider assignment +set strip_len = strip_length() + +# ✅ RECOMMENDED: Computation with existing values +set strip_len2 = (strip_len + 1) / 2 + +# Use computed values in animation parameters +animation stream1 = comet_animation( + color=red + tail_length=strip_len / 4 # Computed: quarter of strip length + speed=1.5 + priority=10 +) +``` + +### ⚠️ Dangerous Patterns (Prevented by Transpiler) + +The transpiler prevents dangerous patterns that would create new value provider instances at each evaluation: + +```berry +# ❌ DANGEROUS: Function creation in computed expression +# This would create a new strip_length() instance at each evaluation +set strip_len3 = (strip_length() + 1) / 2 + +# ❌ ERROR: Transpiler will reject this with: +# "Function 'strip_length()' cannot be used in computed expressions. +# This creates a new instance at each evaluation." +``` + +**Why This Is Dangerous:** +- Creates a new function instance every time the expression is evaluated +- Causes memory leaks and performance degradation +- Each new instance has its own timing and state, leading to inconsistent behavior + +**Safe Alternative:** +```berry +# ✅ CORRECT: Separate the value provider creation from computation +set strip_len = strip_length() # Single value provider +set strip_len3 = (strip_len + 1) / 2 # Computation with existing value +``` + +**Functions That Are Restricted in Computed Expressions:** +- Any function that creates instances (value providers, animations, etc.) when called +- Examples: `strip_length()`, `triangle()`, `smooth()`, `solid()`, etc. + +**Note:** These functions are allowed in `set` statements as they create the instance once, but they cannot be used inside arithmetic expressions that get wrapped in closures, as this would create new instances at each evaluation. + +### Advanced Computed Values + +```berry +# Complex expressions with multiple operations +set base_speed = 2.0 +animation stream2 = comet_animation( + color=blue + tail_length=strip_len / 8 + 2 # Computed: eighth of strip + 2 + speed=base_speed * 1.5 # Computed: base speed × 1.5 +) + +# Computed values in property assignments +stream1.position = strip_len / 2 # Center of strip +stream2.opacity = strip_len * 4 # Scale opacity with strip size + +# Using mathematical functions in computed values +animation pulse = pulsating_animation( + color=red + period=2s +) +pulse.opacity = abs(sine(strip_len) * 128 + 127) # Sine wave opacity +pulse.position = max(0, min(strip_len - 1, round(strip_len / 2))) # Clamped center position +``` + +**Supported Operations:** +- Addition: `+` +- Subtraction: `-` +- Multiplication: `*` +- Division: `/` +- Parentheses for grouping: `(expression)` + +**Mathematical Functions:** +The following mathematical functions are available in computed parameters and are automatically detected by the transpiler: + +| Function | Description | Parameters | Return Value | +|----------|-------------|------------|--------------| +| `min(a, b, ...)` | Returns the minimum value | Two or more numbers | Minimum value | +| `max(a, b, ...)` | Returns the maximum value | Two or more numbers | Maximum value | +| `abs(x)` | Returns the absolute value | One number | Absolute value | +| `round(x)` | Rounds to nearest integer | One number | Rounded integer | +| `sqrt(x)` | Returns the square root | One number | Square root (scaled for integers) | +| `scale(v, from_min, from_max, to_min, to_max)` | Scales value from one range to another | Value and range parameters | Scaled integer | +| `sin(angle)` | Returns sine of angle | Angle in 0-255 range (0-360°) | Sine value in -255 to 255 range | +| `cos(angle)` | Returns cosine of angle | Angle in 0-255 range (0-360°) | Cosine value in -255 to 255 range | + +**Mathematical Function Examples:** +```berry +# Basic math functions +set strip_len = strip_length() +animation test = pulsating_animation(color=red, period=2s) + +# Absolute value for ensuring positive results +test.opacity = abs(strip_len - 200) + +# Min/max for clamping values +test.position = max(0, min(strip_len - 1, 15)) # Clamp position to valid range + +# Rounding for integer positions +test.position = round(strip_len / 2.5) + +# Square root for non-linear scaling +test.brightness = sqrt(strip_len * 4) # Non-linear brightness based on strip size + +# Scaling values between ranges +test.opacity = scale(strip_len, 10, 60, 50, 255) # Scale strip length to opacity range + +# Trigonometric functions for wave patterns +set angle = 128 # 180 degrees in 0-255 range +test.opacity = sin(angle) + 128 # Mathematical sine function (not oscillator) +test.brightness = cos(angle) + 128 # Mathematical cosine function (not oscillator) + +# Complex expressions combining multiple functions +test.position = max(0, round(abs(sin(strip_len * 2)) * (strip_len - 1) / 255)) +test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255))) +``` + +**Special Notes:** +- **Integer Optimization**: `sqrt()` function automatically handles integer scaling for 0-255 range values +- **Trigonometric Range**: `sin()` and `cos()` use 0-255 input range (mapped to 0-360°) and return -255 to 255 output range +- **Automatic Detection**: Mathematical functions are automatically detected at transpile time using dynamic introspection +- **Closure Context**: In computed parameters, mathematical functions are called as `animation._math.()` in the generated closure context + +**How It Works:** +When the DSL detects arithmetic expressions containing value providers, variable references, or mathematical functions, it automatically creates closure functions that capture the computation. These closures are called with `(self, param_name, time_ms)` parameters, allowing the computation to be re-evaluated dynamically as needed. Mathematical functions are automatically prefixed with `animation._math.` in the closure context to access the ClosureValueProvider's mathematical methods. + +**User Functions in Computed Parameters:** +User-defined functions can also be used in computed parameter expressions, providing powerful custom effects: + +```berry +# Simple user function in computed parameter +animation base = solid(color=blue) +base.opacity = rand_demo() + +# User functions mixed with math operations +animation dynamic = solid( + color=purple + opacity=max(50, min(255, rand_demo() + 100)) +) +``` + +### User Functions + +User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time. + +**Available User Functions:** +- `rand_demo()` - Returns random values for demonstration purposes + +**Usage in Computed Parameters:** +```berry +# Simple user function +animation.opacity = rand_demo() + +# User function with math operations +animation.opacity = max(100, rand_demo()) + +# User function in arithmetic expressions +animation.opacity = abs(rand_demo() - 128) + 64 +``` + +**Available User Functions:** +The following user functions are available by default (see [User Functions Guide](User_Functions.md) for details): + +| Function | Parameters | Description | +|----------|------------|-------------| +| `rand_demo()` | none | Returns a random value (0-255) for demonstration | + +**User Function Behavior:** +- User functions are automatically detected by the transpiler +- They receive `self.engine` as the first parameter in closure context +- They can be mixed with mathematical functions and arithmetic operations +- The entire expression is wrapped in a single efficient closure + +## Sequences + +Sequences orchestrate multiple animations with timing control. The DSL supports two syntaxes for sequences with repeat functionality: + +### Basic Sequence Syntax + +```berry +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 +} +``` + +### Repeat Sequence Syntax + +For sequences that are primarily repeating patterns, you can use the alternative syntax: + +```berry +# Option 1: Traditional syntax with repeat sub-sequence +sequence cylon_eye { + repeat forever { + play red_eye for 3s + red_eye.pos = triangle_val + play red_eye for 3s + red_eye.pos = cosine_val + eye_color.next = 1 + } +} + +# Option 2: Alternative syntax - sequence with repeat modifier +sequence cylon_eye repeat forever { + play red_eye for 3s + red_eye.pos = triangle_val + play red_eye for 3s + red_eye.pos = cosine_val + eye_color.next = 1 +} + +# Option 3: Parametric repeat count +sequence rainbow_cycle repeat palette.size times { + play animation for 1s + colors.next = 1 +} +``` + +**Note**: All syntaxes are functionally equivalent. The repeat count can be a literal number, variable, or dynamic expression that evaluates at runtime. + +### Sequence Statements + +#### Play Statement + +```berry +play animation_name # Play indefinitely +play animation_name for 5s # Play for specific duration +play animation_name for duration_var # Play for variable duration +``` + +#### Wait Statement + +```berry +wait 1s # Wait for 1 second +wait 500ms # Wait for 500 milliseconds +wait duration_var # Wait for variable duration +``` + +#### Duration Support + +Both `play` and `wait` statements support flexible duration specifications: + +**Literal Time Values:** +```berry +play animation for 5s # 5 seconds +play animation for 2000ms # 2000 milliseconds +play animation for 1m # 1 minute +``` + +**Variable References:** +```berry +set short_time = 2s +set long_time = 10s + +sequence demo { + play animation for short_time # Use variable duration + wait long_time # Variables work in wait too +} +``` + +**Value Providers (Dynamic Duration):** +```berry +set dynamic_duration = triangle(min_value=1000, max_value=5000, period=10s) + +sequence demo { + play animation for dynamic_duration # Duration changes over time +} +``` + +**Examples:** +```berry +# Cylon eye with variable duration +set eye_duration = 5s + +sequence cylon_eye forever { + play red_eye for eye_duration # Use variable for consistent timing + red_eye.pos = triangle_val + play red_eye for eye_duration # Same duration for both phases + red_eye.pos = cosine_val + eye_color.next = 1 +} +``` + +#### Repeat Statement + +Repeat statements create runtime sub-sequences that execute repeatedly: + +```berry +repeat 3 times { # Repeat exactly 3 times + play animation for 1s + wait 500ms +} + +repeat forever { # Repeat indefinitely until parent sequence stops + play animation for 1s + wait 500ms +} + +repeat col1.palette_size times { # Parametric repeat count using property access + play animation for 1s + col1.next = 1 +} +``` + +**Repeat Count Types:** +- **Literal numbers**: `repeat 5 times` - fixed repeat count +- **Variables**: `repeat count_var times` - using previously defined variables +- **Property access**: `repeat color_provider.palette_size times` - dynamic values from object properties +- **Computed expressions**: `repeat strip_length() / 2 times` - calculated repeat counts + +**Repeat Behavior:** +- **Runtime Execution**: Repeats are executed at runtime, not expanded at compile time +- **Dynamic Evaluation**: Parametric repeat counts are evaluated when the sequence starts +- **Sub-sequences**: Each repeat block creates a sub-sequence that manages its own iteration state +- **Nested Repeats**: Supports nested repeats with multiplication (e.g., `repeat 3 times { repeat 2 times { ... } }` executes 6 times total) +- **Forever Loops**: `repeat forever` continues until the parent sequence is stopped +- **Efficient**: No memory overhead for large repeat counts + +#### Assignment Statement + +Property assignments can be performed within sequences to dynamically modify animation parameters during playback: + +```berry +sequence demo { + play red_eye for 3s + red_eye.pos = triangle_val # Change position to triangle oscillator + play red_eye for 3s + red_eye.pos = cosine_val # Change position to cosine oscillator + eye_color.next = 1 # Advance color cycle to next color +} +``` + +**Assignment Semantics:** +- Assignments in sequences have exactly the same semantics as assignments outside sequences +- They can assign static values, value providers, or computed expressions +- Assignments are executed instantly when the sequence step is reached +- The assignment is wrapped in a closure: `def (engine) end` + +**Examples:** +```berry +sequence dynamic_show { + play pulse_anim for 2s + pulse_anim.opacity = 128 # Set static opacity + play pulse_anim for 2s + pulse_anim.opacity = brightness # Use value provider + play pulse_anim for 2s + pulse_anim.color = next_color # Change color provider + play pulse_anim for 2s +} + +# Assignments work in repeat blocks too +sequence cylon_eye { + repeat 3 times { + play red_eye for 1s + red_eye.pos = triangle_val # Change oscillator pattern + play red_eye for 1s + red_eye.pos = cosine_val # Change back + eye_color.next = 1 # Advance color + } +} +``` + +#### If Statement + +Conditional execution statements that run their body 0 or 1 times based on a boolean condition: + +```berry +if condition { # Execute if condition is true (non-zero) + play animation for 1s + wait 500ms +} +``` + +**Condition Types:** +- **Static values**: `if true { ... }`, `if false { ... }`, `if 5 { ... }` +- **Variables**: `if flag { ... }` - using previously defined variables +- **Template parameters**: `if self.enabled { ... }` - dynamic values from template parameters +- **Computed expressions**: `if strip_length() > 30 { ... }` - calculated conditions + +**If Behavior:** +- **Boolean Coercion**: All conditions are wrapped with `bool()` to ensure 0 or 1 iterations +- **Static Optimization**: Static conditions (literals) are evaluated at compile time without closures +- **Dynamic Evaluation**: Dynamic conditions (variables, parameters) are wrapped in closures +- **Conditional Gate**: Useful for enabling/disabling parts of sequences based on flags + +**Examples:** +```berry +# Static condition +sequence demo { + if true { + play animation for 1s + } +} + +# Template parameter condition +template animation configurable { + param enable_effect type bool default true + + color my_red = 0xFF0000 + animation solid_red = solid(color=my_red) + + sequence main repeat forever { + if enable_effect { + play solid_red for 1s + } + } + + run main +} + +# Variable condition +set flag = true +sequence conditional { + if flag { + play animation for 2s + } +} + +# Bidirectional animation with flags +template animation shutter_bidir { + param ascending type bool default true + param descending type bool default true + + sequence shutter_seq repeat forever { + if ascending { + play shutter_lr for 2s + } + if descending { + play shutter_rl for 2s + } + } + + run shutter_seq +} +``` + +**Comparison with Repeat:** +- `if condition { ... }` - Runs 0 or 1 times (boolean gate) +- `repeat count times { ... }` - Runs exactly `count` times (iteration) + +#### Restart Statements + +Restart statements allow you to restart value providers and animations from their initial state during sequence execution: + +```berry +restart value_provider_name # Restart value provider from beginning +restart animation_name # Restart animation from beginning +``` + +**Restart Statement:** +- Restarts value providers (oscillators, color cycles, etc.) from their initial state +- Restarts animations from their beginning state +- Calls the `start()` method on the value provider or animation, which resets the time origin only if the object was already started previously +- Useful for synchronizing oscillators, restarting color cycles, or restarting complex animations + +**Timing Behavior:** +- The `start()` method only resets the time origin if `self.start_time` is not nil (i.e., the object was already started) +- For fresh objects, the first call to `update()`, `render()`, or `produce_value()` initializes the time reference +- This prevents premature time initialization and ensures proper timing behavior + +**Examples:** +```berry +# Restart oscillators for synchronized movement +sequence sync_demo { + play wave_anim for 3s + restart position_osc # Restart oscillator time origin + play wave_anim for 3s +} + +# Restart animations for clean transitions +sequence clean_transitions { + play comet_anim for 5s + restart comet_anim # Restart from beginning position + play comet_anim for 5s +} +``` + +## Template Animations + +Template animations provide a powerful way to create reusable, parameterized animation classes. They allow you to define animation blueprints that can be instantiated multiple times with different parameters, promoting code reuse and maintainability. + +**Template-Only Files**: DSL files containing only template animation definitions transpile to pure Berry classes without engine initialization or execution code. This allows template animations to be used as reusable animation libraries. + +### Template Animation Definition + +Template animations are defined using the `template animation` keywords followed by a parameter block and body: + +```berry +template animation shutter_effect { + param colors type palette nillable true + param duration type time min 0 max 3600 default 5 nillable false + + set strip_len = strip_length() + set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration) + + color col = color_cycle(colors=colors, period=0) + + animation shutter = beacon_animation( + color = col + pos = strip_len / 2 + beacon_size = shutter_size + priority = 5 + ) + + sequence seq repeat forever { + restart shutter_size + play shutter for duration + col.next = 1 + } + + run seq +} + +# Use the template animation +palette rainbow = [red, orange, yellow, green, blue, indigo, white] +animation my_shutter = shutter_effect(colors=rainbow, duration=2s) +run my_shutter +``` + +**Code Generation:** +Template animations generate Berry classes extending `engine_proxy`: + +```berry +class shutter_effect_animation : animation.engine_proxy + static var PARAMS = animation.enc_params({ + "colors": {"type": "palette"}, + "duration": {"type": "time", "min": 0, "max": 3600, "default": 5} + }) + + def init(engine) + super(self).init(engine) + # Generated code with self.colors and self.duration references + self.add(seq_) + end +end +``` + +**Parameter Constraints:** +Template animation parameters support constraints: +- `type` - Parameter type (palette, time, int, color, etc.) +- `min` - Minimum value (for numeric types) +- `max` - Maximum value (for numeric types) +- `default` - Default value +- `nillable` - Whether parameter can be nil (true/false) + +**Implicit Parameters:** +Template animations automatically inherit parameters from the `engine_proxy` class hierarchy. These parameters are available without explicit declaration and can be used directly in your template animation body: + +```berry +# These parameters are implicitly available in all template animations: +param id type string default "animation" +param priority type int default 10 +param duration type int default 0 +param loop type bool default false +param opacity type int default 255 +param color type int default 0 +param is_running type bool default false +``` + +**Example using implicit parameters:** +```berry +template animation fade_effect { + param colors type palette + + # 'duration' is an implicit parameter - no need to declare it + set oscillator = sawtooth(min_value=0, max_value=255, duration=duration) + + color col = color_cycle(colors=colors, period=0) + animation test = solid(color=col) + + # 'opacity' is also implicit + test.opacity = oscillator + + run test +} + +# When instantiating, you can set implicit parameters +animation my_fade = fade_effect(colors=rainbow) +my_fade.duration = 5000 # Set the implicit duration parameter +my_fade.opacity = 200 # Set the implicit opacity parameter +``` + +**Notes on Implicit Parameters:** +- Implicit parameters can be overridden by explicit declarations if needed +- They follow the same constraint rules as explicit parameters +- They are accessed as `self.` within the template body +- All implicit parameters come from the `Animation` and `ParameterizedObject` base classes + +**Key Features:** +- Generates reusable animation classes extending `engine_proxy` +- Parameters accessed as `self.` within the template body +- Uses `self.add()` to add child animations +- Can be instantiated multiple times with different parameters +- Supports parameter constraints (type, min, max, default, nillable) + +### Template Parameter Validation + +The DSL transpiler provides comprehensive validation for template parameters to ensure code quality and catch errors early: + +**Parameter Name Validation:** +- **Duplicate Detection**: Prevents using the same parameter name twice +- **Reserved Keywords**: Prevents conflicts with Berry keywords (`animation`, `color`, `def`, etc.) +- **Built-in Colors**: Prevents conflicts with predefined color names (`red`, `blue`, etc.) + +```berry +template bad_example { + param color type color # ❌ Error: conflicts with built-in color + param animation type number # ❌ Error: conflicts with reserved keyword + param my_param type color + param my_param type number # ❌ Error: duplicate parameter name +} +``` + +**Type Annotation Validation:** + +Valid parameter types for `static var PARAMS` and template parameters: + +| Type | Description | Synonym For | Example | +|------|-------------|-------------|---------| +| `int` | Integer values | - | `{"type": "int", "default": 100}` | +| `bool` | Boolean values | - | `{"type": "bool", "default": false}` | +| `string` | String values | - | `{"type": "string", "default": "name"}` | +| `bytes` | Byte buffers (palettes) | - | `{"type": "bytes", "default": bytes("FF0000")}` | +| `function` | Functions/closures | - | `{"type": "function", "default": nil}` | +| `animation` | Animation instances | - | Symbol table tracking | +| `value_provider` | Value provider instances | - | Symbol table tracking | +| `number` | Generic numeric type | - | Numeric constraints only | +| `any` | Any type (no validation) | - | `{"type": "any", "default": nil}` | +| `color` | Color values | `int` | `{"type": "color", "default": 0xFFFF0000}` | +| `palette` | Palette definitions | `bytes` | `{"type": "palette", "default": bytes(...)}` | +| `time` | Time values (ms) | `int` | `{"type": "time", "default": 5000}` | +| `percentage` | Percentage (0-255) | `int` | `{"type": "percentage", "default": 128}` | + +**Note:** Types `color`, `palette`, `time`, and `percentage` are user-friendly synonyms that map to their base types during validation. + +```berry +template type_example { + param my_color type invalid_type # ❌ Error: invalid type annotation + param valid_color type color # ✅ Valid type annotation +} +``` + +**Parameter Usage Validation:** +The transpiler generates **warnings** (not errors) for unused parameters: + +```berry +template unused_example { + param used_color type color + param unused_param type number # ⚠️ Warning: parameter never used + + animation test = solid(color=used_color) + run test +} +``` + +**Validation Benefits:** +- **Early Error Detection**: Catches parameter issues at compile time +- **Clear Error Messages**: Provides helpful suggestions for fixing issues +- **Code Quality**: Encourages proper parameter naming and usage +- **Warnings vs Errors**: Unused parameters generate warnings that don't prevent compilation + +## Execution Statements + +Execute animations or sequences: + +```berry +run animation_name # Run an animation +run sequence_name # Run a sequence +``` + +### Debug and Logging + +Log debug messages during animation execution: + +```berry +log("Debug message") # Log message at level 3 +log("Animation started") # Useful for debugging sequences +log("Color changed to red") # Track animation state changes +``` + +**Log Function Behavior:** +- Accepts string literals only (no variables or expressions) +- Transpiles to Berry `log(f"message", 3)` +- Messages are logged at level 3 for debugging purposes +- Can be used anywhere in DSL code: standalone, in sequences, etc. + +## Operators and Expressions + +### Arithmetic Operators + +```berry ++ # Addition +- # Subtraction (also unary minus) +* # Multiplication +/ # Division +% # Modulo +``` + +### Comparison Operators + +```berry +== # Equal to +!= # Not equal to +< # Less than +<= # Less than or equal +> # Greater than +>= # Greater than or equal +``` + +### Logical Operators + +```berry +&& # Logical AND +|| # Logical OR +! # Logical NOT +``` + +### Assignment Operators + +```berry += # Simple assignment +``` + +## Function Calls + +Functions use named parameter syntax with flexible formatting: + +```berry +# Single line (commas required) +function_name(param1=value1, param2=value2) + +# Multi-line (commas optional when parameters are on separate lines) +function_name( + param1=value1 + param2=value2 + param3=value3 +) + +# Mixed syntax (both commas and newlines work) +function_name( + param1=value1, param2=value2 + param3=value3 +) +``` + +**Examples:** +```berry +# Traditional single-line syntax +solid(color=red) +pulsating_animation(color=blue, period=2s) + +# New multi-line syntax (no commas needed) +pulsating_animation( + color=blue + period=2s + brightness=255 +) + +# Mixed syntax +comet_animation( + color=stream_pattern, tail_length=15 + speed=1.5s + priority=10 +) +``` + +**Nested Function Calls:** +```berry +pulsating_animation( + color=solid(color=red) + period=smooth( + min_value=1000 + max_value=3000 + period=10s + ) +) +``` + +**Mathematical Functions in Computed Parameters:** +Mathematical functions can be used in computed parameter expressions and are automatically detected by the transpiler: + +```berry +animation wave = pulsating_animation( + color=blue + period=2s +) + +# Mathematical functions in property assignments +wave.opacity = abs(sine(strip_length()) - 128) # Sine wave opacity +wave.position = max(0, min(strip_length() - 1, 15)) # Clamped position +wave.brightness = round(sqrt(strip_length()) * 4) # Non-linear scaling +``` + +## Supported Classes + +### Value Providers + +Value providers create dynamic values that change over time: + +| Function | Description | +|----------|-------------| +| `static_value` | Returns a constant value | +| `strip_length` | Returns the LED strip length in pixels | +| `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) | +| `cosine_osc` | Cosine wave oscillation (alias for smooth - cosine waveform) | +| `sine_osc` | 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 | + +```berry +# 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 +cosine_osc(min_value=3, max_value=1, period=5s) # Cosine wave (alias for smooth) +sine_osc(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) +set sine_wave = sine_osc(min_value=0, max_value=255, period=2s) +set cosine_wave = cosine_osc(min_value=50, max_value=200, period=3s) +set strip_len = strip_length() # Get the current strip length +``` + +### 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_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 | +| `wave_animation` | Wave propagation 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 +- **Property Assignment Validation**: Validates parameter names in property assignments (e.g., `animation.invalid_param = value`) against the actual class parameters +- **Object Reference Validation**: Validates that referenced objects exist in `run` statements and sequence `play` statements + +### Common Errors + +```berry +# Invalid: Redefining predefined color +color red = 0x800000 # Error: Cannot redefine 'red' + +# Invalid: Unknown parameter in constructor +animation bad = pulsating_animation(invalid_param=123) # Error: Unknown parameter + +# Invalid: Unknown parameter in property assignment +animation pulse = pulsating_animation(color=red, period=2s) +pulse.wrong_arg = 15 # Error: Parameter 'wrong_arg' not valid for PulseAnimation + +# Invalid: Undefined reference in color definition +animation ref = solid(color=undefined_color) # Error: Undefined reference + +# Invalid: Undefined reference in run statement +run undefined_animation # Error: Undefined reference 'undefined_animation' in run + +# Invalid: Undefined reference in sequence +sequence demo { + play undefined_animation for 5s # Error: Undefined reference 'undefined_animation' in sequence play +} + +# Valid alternatives +color my_red = 0x800000 # OK: Different name +animation good = pulsating_animation(color=red, period=2s) # OK: Valid parameters +good.priority = 10 # OK: Valid parameter assignment +``` + +## Formal Grammar (EBNF) + +```ebnf +(* Animation DSL Grammar *) + +program = { statement } ; + +statement = import_stmt + | config_stmt + | definition + | property_assignment + | sequence + | template_def + | external_stmt + | execution_stmt ; + +(* Import and Configuration *) +import_stmt = "import" identifier ; +config_stmt = variable_assignment ; +external_stmt = "extern" "function" identifier ; +(* strip_config = "strip" "length" number ; -- TEMPORARILY DISABLED *) +variable_assignment = "set" identifier "=" expression ; + +(* Definitions *) +definition = color_def | palette_def | animation_def | template_animation_def ; +color_def = "color" identifier "=" color_expression ; +palette_def = "palette" identifier "=" palette_array ; +animation_def = "animation" identifier "=" animation_expression ; +template_animation_def = "template" "animation" identifier "{" template_body "}" ; + +(* Property Assignments *) +property_assignment = identifier "." identifier "=" expression ; + +(* Sequences *) +sequence = "sequence" identifier [ "repeat" ( expression "times" | "forever" ) ] "{" sequence_body "}" ; +sequence_body = { sequence_statement } ; +sequence_statement = play_stmt | wait_stmt | repeat_stmt | if_stmt | sequence_assignment | restart_stmt ; + +play_stmt = "play" identifier [ "for" time_expression ] ; +wait_stmt = "wait" time_expression ; +repeat_stmt = "repeat" ( expression "times" | "forever" ) "{" sequence_body "}" ; +if_stmt = "if" expression "{" sequence_body "}" ; +sequence_assignment = identifier "." identifier "=" expression ; +restart_stmt = "restart" identifier ; + +(* Template Animations *) +template_animation_def = "template" "animation" identifier "{" template_body "}" ; +template_body = { template_statement } ; +template_statement = param_decl | color_def | palette_def | animation_def | property_assignment | sequence_def | execution_stmt ; +param_decl = "param" identifier [ "type" identifier ] [ constraint_list ] ; +constraint_list = ( "min" number | "max" number | "default" expression | "nillable" boolean ) { constraint_list } ; + +(* 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" ; +``` + +## Flexible Parameter Syntax + +The DSL supports flexible parameter syntax that makes multi-line function calls more readable: + +### Traditional Syntax (Commas Required) +```berry +animation stream = comet_animation(color=red, tail_length=15, speed=1.5s, priority=10) +``` + +### New Multi-Line Syntax (Commas Optional) +```berry +animation stream = comet_animation( + color=red + tail_length=15 + speed=1.5s + priority=10 +) +``` + +### Mixed Syntax (Both Supported) +```berry +animation stream = comet_animation( + color=red, tail_length=15 + speed=1.5s + priority=10 +) +``` + +### Rules +- **Single line**: Commas are required between parameters +- **Multi-line**: Commas are optional when parameters are on separate lines +- **Mixed**: You can use both commas and newlines as separators +- **Comments**: Inline comments work with both syntaxes + +This applies to: +- Animation function calls +- Color provider function calls +- Value provider function calls +- Palette entries + +## 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, if) +- **Conditional execution**: `if` statement for boolean-based conditional execution +- Variable assignments with type conversion +- Reserved name validation +- Parameter validation at compile time +- Execution statements +- **Template animations**: Reusable animation classes with parameters extending `engine_proxy` +- User-defined functions (with engine-first parameter pattern) - see **[User Functions Guide](User_Functions.md)** +- **External function declarations**: `extern` keyword to declare Berry functions defined in `berry` blocks for DSL use +- **User functions in computed parameters**: User functions can be used in arithmetic expressions alongside mathematical functions +- **Flexible parameter syntax**: Commas optional when parameters are on separate lines +- **Computed values**: Arithmetic expressions with value providers automatically create closures +- **Mathematical functions**: `min`, `max`, `abs`, `round`, `sqrt`, `scale`, `sine`, `cosine` in computed parameters + +### 🚧 Partially Implemented +- Expression evaluation (basic support) +- Nested function calls (working but limited) +- Error recovery (basic error reporting) + +### ❌ Planned Features +- Advanced control flow (else, elif, choose random) +- Event system and handlers +- 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. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Dsl_Transpilation.md b/lib/libesp32/berry_animation/animation_docs/Dsl_Transpilation.md new file mode 100644 index 000000000..2e1165824 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Dsl_Transpilation.md @@ -0,0 +1,798 @@ +# DSL Reference - Berry Animation Framework + +This document provides a comprehensive reference for the Animation DSL (Domain-Specific Language), which allows you to define animations using a declarative syntax with named parameters. + +## Module Import + +The DSL functionality is provided by a separate module: + +```berry +import animation # Core framework (required) +import animation_dsl # DSL compiler and runtime (required for DSL) +``` + +## Why Use the DSL? + +### Benefits +- **Declarative syntax**: Describe what you want, not how to implement it +- **Readable code**: Natural language-like syntax +- **Rapid prototyping**: Quick iteration on animation ideas +- **Event-driven**: Built-in support for interactive animations +- **Composition**: Easy layering and sequencing of animations + +### When to Use DSL vs Programmatic + +**Use DSL when:** +- Creating complex animation sequences +- Building interactive, event-driven animations +- Rapid prototyping and experimentation +- Non-programmers need to create animations +- You want declarative, readable animation definitions + +**Use programmatic API when:** +- Building reusable animation components +- Performance is critical (DSL has compilation overhead) +- You need fine-grained control over animation logic +- Integrating with existing Berry code +- Firmware size is constrained (DSL module can be excluded) + +## Transpiler Architecture + +For detailed information about the DSL transpiler's internal architecture, including the core processing flow and expression processing chain, see [Transpiler_Architecture.md](Transpiler_Architecture.md). + +## DSL API Functions + +### Core Functions + +#### `animation_dsl.compile(source)` +Compiles DSL source code to Berry code without executing it. + +```berry +var dsl_source = "color red = 0xFF0000\n" + "animation red_anim = solid(color=red)\n" + "run red_anim" + +var berry_code = animation_dsl.compile(dsl_source) +print(berry_code) # Shows generated Berry code +``` + +#### `animation_dsl.execute(source)` +Compiles and executes DSL source code in one step. + +```berry +animation_dsl.execute("color blue = 0x0000FF\n" + "animation blue_anim = solid(color=blue)\n" + "run blue_anim for 5s") +``` + +#### `animation_dsl.load_file(filename)` +Loads DSL source from a file and executes it. + +```berry +# Create a DSL file +var f = open("my_animation.dsl", "w") +f.write("color green = 0x00FF00\n" + "animation pulse_green = pulsating_animation(color=green, period=2s)\n" + "run pulse_green") +f.close() + +# Load and execute +animation_dsl.load_file("my_animation.dsl") +``` + +## DSL Language Overview + +The Animation DSL uses a declarative syntax with named parameters. All animations are created with an engine-first pattern and parameters are set individually for maximum flexibility. + +### Key Syntax Features + +- **Import statements**: `import module_name` for loading Berry modules +- **Named parameters**: All function calls use `name=value` syntax +- **Time units**: `2s`, `500ms`, `1m`, `1h` +- **Hex colors**: `0xFF0000`, `0x80FF0000` (ARGB) +- **Named colors**: `red`, `blue`, `white`, etc. +- **Comments**: `# This is a comment` +- **Property assignment**: `animation.property = value` +- **User functions**: `function_name()` for custom functions + +### Basic Structure + +```berry +# Import statements (optional, for user functions or custom modules) +import user_functions + +# Optional strip configuration +strip length 60 + +# Color definitions +color red = 0xFF0000 +color blue = 0x0000FF + +# Animation definitions with named parameters +animation pulse_red = pulsating_animation(color=red, period=2s) +animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500) + +# Property assignments with user functions +pulse_red.priority = 10 +pulse_red.opacity = breathing_effect() +comet_blue.direction = -1 + +# Execution +run pulse_red + +``` + +The DSL transpiles to Berry code where each animation gets an engine parameter and named parameters are set individually. + +## Symbol Resolution + +The DSL transpiler uses intelligent symbol resolution at compile time to optimize generated code and eliminate runtime lookups: + +### Transpile-Time Symbol Resolution + +When the DSL encounters an identifier (like `SINE` or `red`), it checks at transpile time whether the symbol exists in the `animation` module using Berry's introspection capabilities: + +```berry +# If SINE exists in animation module +animation wave = wave_animation(waveform=SINE) +# Transpiles to: animation.SINE (direct access) + +# If custom_color doesn't exist in animation module +color custom_color = 0xFF0000 +animation solid_red = solid(color=custom_color) +# Transpiles to: custom_color_ (user-defined variable) +``` + +### Benefits + +- **Performance**: Eliminates runtime symbol lookups for built-in constants +- **Error Detection**: Catches undefined symbols at compile time +- **Code Clarity**: Generated Berry code clearly shows built-in vs user-defined symbols +- **Optimization**: Direct access to animation module symbols is faster + +### Symbol Categories + +**Built-in Symbols** (resolved to `animation.`): +- Animation factory functions: `solid`, `pulsating_animation`, `comet_animation` +- Value providers: `triangle`, `smooth`, `sine`, `static_value` +- Color providers: `color_cycle`, `breathe_color`, `rich_palette` +- Constants: `PALETTE_RAINBOW`, `SINE`, `TRIANGLE`, etc. + +**User-defined Symbols** (resolved to `_`): +- Custom colors: `my_red`, `fire_color` +- Custom animations: `pulse_effect`, `rainbow_wave` +- Variables: `brightness_level`, `cycle_time` + +### Property Assignment Resolution + +Property assignments also use the same resolution logic: + +```berry +# Built-in symbol (if 'engine' existed in animation module) +engine.brightness = 200 +# Would transpile to: animation.engine.brightness = 200 + +# User-defined symbol +my_animation.priority = 10 +# Transpiles to: my_animation_.priority = 10 +``` + +This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code. + +## Import Statement Transpilation + +The DSL supports importing Berry modules using the `import` keyword, which provides a clean way to load user functions and custom modules. + +### Import Syntax + +```berry +# DSL Import Syntax +import user_functions +import my_custom_module +import math +``` + +### Transpilation Behavior + +Import statements are transpiled directly to Berry import statements with quoted module names: + +```berry +# DSL Code +import user_functions + +# Transpiles to Berry Code +import "user_functions" +``` + +### Import Processing + +1. **Early Processing**: Import statements are processed early in transpilation +2. **Module Loading**: Imported modules are loaded using standard Berry import mechanism +3. **Function Registration**: User function modules should register functions using `animation.register_user_function()` +4. **No Validation**: The DSL doesn't validate module existence at compile time + +### Example Import Workflow + +**Step 1: Create User Functions Module (`user_functions.be`)** +```berry +import animation + +def rand_demo(engine) + import math + return math.rand() % 256 +end + +# Register for DSL use +animation.register_user_function("rand_demo", rand_demo) +``` + +**Step 2: Use in DSL** +```berry +import user_functions + +animation test = solid(color=blue) +test.opacity = rand_demo() +run test +``` + +**Step 3: Generated Berry Code** +```berry +import animation +var engine = animation.init_strip() + +import "user_functions" +var test_ = animation.solid(engine) +test_.color = 0xFF0000FF +test_.opacity = animation.create_closure_value(engine, + def (engine) return animation.get_user_function('rand_demo')(engine) end) +engine.add(test_) +engine.run() +``` + +## Berry Code Block Transpilation + +The DSL supports embedding arbitrary Berry code using the `berry` keyword with triple-quoted strings. This provides an escape hatch for complex logic while maintaining the declarative nature of the DSL. + +### Berry Code Block Syntax + +```berry +# DSL Berry Code Block +berry """ +import math +var custom_value = math.pi * 2 +print("Custom calculation:", custom_value) +""" +``` + +### Transpilation Behavior + +Berry code blocks are copied verbatim to the generated Berry code with comment markers: + +```berry +# DSL Code +berry """ +var test_var = 42 +print("Hello from berry block") +""" + +# Transpiles to Berry Code +# Berry code block +var test_var = 42 +print("Hello from berry block") +# End berry code block +``` + +### Integration with DSL Objects + +Berry code can interact with DSL-generated objects by using the underscore suffix naming convention: + +```berry +# DSL Code +animation pulse = pulsating_animation(color=red, period=2s) +berry """ +pulse_.opacity = 200 +pulse_.priority = 10 +""" + +# Transpiles to Berry Code +var pulse_ = animation.pulsating_animation(engine) +pulse_.color = animation.red +pulse_.period = 2000 +# Berry code block +pulse_.opacity = 200 +pulse_.priority = 10 +# End berry code block +``` + +## Advanced DSL Features + +### Templates + +The DSL supports two types of templates: regular templates (functions) and template animations (classes). + +#### Template Animation Transpilation + +Template animations create reusable animation classes extending `engine_proxy`: + +```berry +# DSL Template Animation +template animation shutter_effect { + param colors type palette nillable true + param duration type time min 0 max 3600 default 5 nillable false + + set strip_len = strip_length() + color col = color_cycle(colors=colors, period=0) + + animation shutter = beacon_animation( + color = col + beacon_size = strip_len / 2 + ) + + sequence seq repeat forever { + play shutter for duration + col.next = 1 + } + + run seq +} +``` + +**Transpiles to:** + +```berry +class shutter_effect_animation : animation.engine_proxy + static var PARAMS = animation.enc_params({ + "colors": {"type": "palette", "nillable": true}, + "duration": {"type": "time", "min": 0, "max": 3600, "default": 5, "nillable": false} + }) + + def init(engine) + super(self).init(engine) + + var strip_len_ = animation.strip_length(engine) + var col_ = animation.color_cycle(engine) + col_.colors = animation.create_closure_value(engine, def (engine) return self.colors end) + col_.period = 0 + + var shutter_ = animation.beacon_animation(engine) + shutter_.color = col_ + shutter_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) / 2 end) + + var seq_ = animation.sequence_manager(engine, -1) + .push_play_step(shutter_, animation.resolve(self.duration)) + .push_closure_step(def (engine) col_.next = 1 end) + + self.add(seq_) + end +end +``` + +**Key Features:** +- Parameters accessed as `self.` and wrapped in closures +- Constraints (min, max, default, nillable) encoded in PARAMS +- Uses `self.add()` instead of `engine.add()` +- Can be instantiated multiple times with different parameters + +#### Regular Template Transpilation + +Regular templates generate Berry functions: + +```berry +# DSL Template +template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse +} +``` + +**Transpiles to:** + +```berry +def pulse_effect_template(engine, color_, speed_) + var pulse_ = animation.pulsating_animation(engine) + pulse_.color = color_ + pulse_.period = speed_ + engine.add(pulse_) +end + +animation.register_user_function('pulse_effect', pulse_effect_template) +``` + +#### Template vs Template Animation + +**Template Animation** (`template animation`): +- Generates classes extending `engine_proxy` +- Parameters accessed as `self.` +- Supports parameter constraints (min, max, default, nillable) +- Uses `self.add()` for composition +- Can be instantiated multiple times + +**Regular Template** (`template`): +- Generates functions +- Parameters accessed as `_` +- Uses `engine.add()` for execution +- Called like functions + +### User-Defined Functions + +Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments: + +```berry +# Define custom function in Berry - engine must be first parameter +def custom_twinkle(engine, color, count, period) + var anim = animation.twinkle_animation(engine) + anim.color = color + anim.count = count + atml:parameter> + + return anim +end + +# Register the function for DSL use +animation.register_user_function("twinkle", custom_twinkle) +``` + +```berry +# Use in DSL - engine is automatically passed as first argument +animation gold_twinkle = twinkle(0xFFD700, 8, 500ms) +animation blue_twinkle = twinkle(blue, 12, 300ms) +run gold_twinkle +``` + +**Important**: The DSL transpiler automatically passes `engine` as the first argument to all user functions. Your function signature must include `engine` as the first parameter, but DSL users don't need to provide it when calling the function. + +For comprehensive examples and best practices, see the **[User Functions Guide](User_Functions.md)**. + +### Event System + +Define event handlers that respond to triggers: + +```berry +# Define animations for different states +color normal = 0x000080 +color alert = 0xFF0000 + +animation normal_state = solid(color=normal) +animation alert_state = pulsating_animation(color=alert, period=500ms) + +# Event handlers +on button_press { + run alert_state for 3s + run normal_state +} + +on sensor_trigger { + run alert_state for 5s + wait 1s + run normal_state +} + +# Default state +run normal_state +``` + +### Nested Function Calls + +DSL supports nested function calls for complex compositions: + +```berry +# Nested calls in animation definitions (now supported) +animation complex = pulsating_animation( + color=red, + period=2s +) + +# Nested calls in run statements +sequence demo { + play pulsating_animation(color=blue, period=1s) for 10s +} +``` + +## Error Handling + +The DSL compiler validates classes and parameters at transpilation time, catching errors before execution: + +```berry +var invalid_dsl = "color red = #INVALID_COLOR\n" + "animation bad = unknown_function(red)\n" + "animation pulse = pulsating_animation(invalid_param=123)" + +try + animation_dsl.execute(invalid_dsl) +except .. as e + print("DSL Error:", e) +end +``` + +### Transpilation-Time Validation + +The DSL performs comprehensive validation during compilation: + +**Animation Factory Validation:** +```berry +# Error: Function doesn't exist +animation bad = nonexistent_animation(color=red) +# Transpiler error: "Animation factory function 'nonexistent_animation' does not exist" + +# Error: Function exists but doesn't create animation +animation bad2 = math_function(value=10) +# Transpiler error: "Function 'math_function' does not create an animation instance" +``` + +**Parameter Validation:** +```berry +# Error: Invalid parameter name in constructor +animation pulse = pulsating_animation(invalid_param=123) +# Transpiler error: "Parameter 'invalid_param' is not valid for pulsating_animation" + +# Error: Invalid parameter name in property assignment +animation pulse = pulsating_animation(color=red, period=2s) +pulse.wrong_arg = 15 +# Transpiler error: "Animation 'PulseAnimation' does not have parameter 'wrong_arg'" + +# Error: Parameter constraint violation +animation comet = comet_animation(tail_length=-5) +# Transpiler error: "Parameter 'tail_length' value -5 violates constraint: min=1" +``` + +**Color Provider Validation:** +```berry +# Error: Color provider doesn't exist +color bad = nonexistent_color_provider(period=2s) +# Transpiler error: "Color provider factory 'nonexistent_color_provider' does not exist" + +# Error: Function exists but doesn't create color provider +color bad2 = pulsating_animation(color=red) +# Transpiler error: "Function 'pulsating_animation' does not create a color provider instance" +``` + +**Reference Validation:** +```berry +# Error: Undefined color reference +animation pulse = pulsating_animation(color=undefined_color) +# Transpiler error: "Undefined reference: 'undefined_color'" + +# Error: Undefined animation reference in run statement +run nonexistent_animation +# Transpiler error: "Undefined reference 'nonexistent_animation' in run" + +# Error: Undefined animation reference in sequence +sequence demo { + play nonexistent_animation for 5s +} +# Transpiler error: "Undefined reference 'nonexistent_animation' in sequence play" +``` + +**Function Call Safety Validation:** +```berry +# Error: Dangerous function creation in computed expression +set strip_len3 = (strip_length() + 1) / 2 +# Transpiler error: "Function 'strip_length()' cannot be used in computed expressions. +# This creates a new instance at each evaluation. Use either: +# set var_name = strip_length() # Single function call +# set computed = (existing_var + 1) / 2 # Computation with existing values" +``` + +**Why This Validation Exists:** +The transpiler prevents dangerous patterns where functions that create instances are called inside computed expressions that get wrapped in closures. This would create a new instance every time the closure is evaluated, leading to: +- Memory leaks +- Performance degradation +- Inconsistent behavior due to multiple timing states + +**Safe Alternative:** +```berry +# ✅ CORRECT: Separate function call from computation +set strip_len = strip_length() # Single function call +set strip_len3 = (strip_len + 1) / 2 # Computation with existing value +``` + +**Template Parameter Validation:** +```berry +# Error: Duplicate parameter names +template bad_template { + param color type color + param color type number # Error: duplicate parameter name +} +# Transpiler error: "Duplicate parameter name 'color' in template" + +# Error: Reserved keyword as parameter name +template reserved_template { + param animation type color # Error: conflicts with reserved keyword +} +# Transpiler error: "Parameter name 'animation' conflicts with reserved keyword" + +# Error: Built-in color name as parameter +template color_template { + param red type number # Error: conflicts with built-in color +} +# Transpiler error: "Parameter name 'red' conflicts with built-in color name" + +# Error: Invalid type annotation +template type_template { + param value type invalid_type # Error: invalid type +} +# Transpiler error: "Invalid parameter type 'invalid_type'. Valid types are: [...]" + +# Warning: Unused parameter (compilation succeeds) +template unused_template { + param used_color type color + param unused_param type number # Warning: never used + + animation test = solid(color=used_color) + run test +} +# Transpiler warning: "Template 'unused_template' parameter 'unused_param' is declared but never used" +``` + +### Error Categories + +- **Syntax errors**: Invalid DSL syntax (lexer/parser errors) +- **Factory validation**: Non-existent or invalid animation/color provider factories +- **Parameter validation**: Invalid parameter names in constructors or property assignments +- **Template validation**: Invalid template parameter names, types, or usage patterns +- **Constraint validation**: Parameter values that violate defined constraints (min/max, enums, types) +- **Reference validation**: Using undefined colors, animations, or variables +- **Type validation**: Incorrect parameter types or incompatible assignments +- **Safety validation**: Dangerous patterns that could cause memory leaks or performance issues +- **Runtime errors**: Errors during Berry code execution (rare with good validation) + +### Warning Categories + +The DSL transpiler also generates **warnings** that don't prevent compilation but indicate potential code quality issues: + +- **Unused parameters**: Template parameters that are declared but never used in the template body +- **Code quality**: Suggestions for better coding practices + +**Warning Behavior:** +- Warnings are included as comments in the generated Berry code +- Compilation succeeds even with warnings present +- Warnings help maintain code quality without being overly restrictive + +## Performance Considerations + +### DSL vs Programmatic Performance + +- **DSL compilation overhead**: ~10-50ms depending on complexity +- **Generated code performance**: Identical to hand-written Berry code +- **Memory usage**: DSL compiler uses temporary memory during compilation + +### Optimization Tips + +1. **Compile once, run many times**: + ```berry + var compiled = animation_dsl.compile(dsl_source) + var fn = compile(compiled) + + # Run multiple times without recompilation + fn() # First execution + fn() # Subsequent executions are faster + ``` + +2. **Use programmatic API for performance-critical code**: + ```berry + # DSL for high-level structure + animation_dsl.execute( + "sequence main {\n" + "play performance_critical_anim for 10s\n" + "}\n" + "run main" + ) + + # Programmatic for performance-critical animations + var performance_critical_anim = animation.create_optimized_animation() + ``` + +## Integration Examples + +### With Tasmota Rules + +```berry +# In autoexec.be +import animation +import animation_dsl + +def handle_rule_trigger(event) + if event == "motion" + animation_dsl.execute("color alert = 0xFF0000\n" + "animation alert_anim = pulsating_animation(color=alert, period=500ms)\n" + "run alert_anim for 5s") + elif event == "door" + animation_dsl.execute("color welcome = 0x00FF00\n" + "animation welcome_anim = breathe_animation(color=welcome, period=2s)\n" + "run welcome_anim for 8s") + end +end + +# Register with Tasmota's rule system +tasmota.add_rule("motion", handle_rule_trigger) +``` + +### With Web Interface + +```berry +# Create web endpoints for DSL execution +import webserver + +def web_execute_dsl() + var dsl_code = webserver.arg("dsl") + if dsl_code + try + animation_dsl.execute(dsl_code) + webserver.content_response("DSL executed successfully") + except .. as e + webserver.content_response(f"DSL Error: {e}") + end + else + webserver.content_response("No DSL code provided") + end +end + +webserver.on("/execute_dsl", web_execute_dsl) +``` + +## Best Practices + +1. **Structure your DSL files**: + ```berry + # Strip configuration first + strip length 60 + + # Colors next + color red = 0xFF0000 + color blue = 0x0000FF + + # Animations with named parameters + animation red_solid = solid(color=red) + animation pulse_red = pulsating_animation(color=red, period=2s) + + # Property assignments + pulse_red.priority = 10 + + # Sequences + sequence demo { + play pulse_red for 5s + } + + # Execution last + run demo + ``` + +2. **Use meaningful names**: + ```berry + # Good + color warning_red = 0xFF0000 + animation door_alert = pulsating_animation(color=warning_red, period=500ms) + + # Avoid + color c1 = 0xFF0000 + animation a1 = pulsating_animation(color=c1, period=500ms) + ``` + +3. **Comment your DSL**: + ```berry + # Security system colors + color normal_blue = 0x000080 # Idle state + color alert_red = 0xFF0000 # Alert state + color success_green = 0x00FF00 # Success state + + # Main security animation sequence + sequence security_demo { + play solid(color=normal_blue) for 10s # Normal operation + play pulsating_animation(color=alert_red, period=500ms) for 3s # Alert + play breathe_animation(color=success_green, period=2s) for 5s # Success confirmation + } + ``` + +4. **Organize complex projects**: + ```berry + # Load DSL modules + animation_dsl.load_file("colors.dsl") # Color definitions + animation_dsl.load_file("animations.dsl") # Animation library + animation_dsl.load_file("sequences.dsl") # Sequence definitions + animation_dsl.load_file("main.dsl") # Main execution + ``` + +This completes the DSL reference documentation. The DSL provides a powerful, declarative way to create complex animations while maintaining the option to use the lightweight programmatic API when needed. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Examples.md b/lib/libesp32/berry_animation/animation_docs/Examples.md new file mode 100644 index 000000000..4c9ee1404 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Examples.md @@ -0,0 +1,479 @@ +# Examples + +Essential examples showcasing the Tasmota Berry Animation Framework using DSL syntax. + +## Basic Animations + +### 1. Solid Color +```berry +color red = 0xFF0000 +animation red_solid = solid(color=red) +run red_solid +``` + +### 2. Pulsing Effect +```berry +color blue = 0x0000FF +animation blue_pulse = pulsating_animation(color=blue, period=2s) +run blue_pulse +``` + +### 3. Moving Comet +```berry +color cyan = 0x00FFFF +animation comet_trail = comet_animation(color=cyan, tail_length=8, speed=100ms, direction=1) +run comet_trail +``` + +## Using Value Providers + +### 4. Breathing Effect +```berry +set breathing = smooth(min_value=50, max_value=255, period=3s) +color white = 0xFFFFFF +animation breathing_white = solid(color=white) +breathing_white.opacity = breathing +run breathing_white +``` + +### 5. Color Cycling +```berry +color rainbow = rainbow_color_provider(period=5s) +animation rainbow_cycle = solid(color=rainbow) +run rainbow_cycle +``` + +## Palette Animations + +### 6. Fire Effect +```berry +palette fire_colors = [ + (0, 0x000000), # Black + (128, 0xFF0000), # Red + (192, 0xFF8000), # Orange + (255, 0xFFFF00) # Yellow +] + +animation fire_effect = palette_animation(colors=fire_colors, period=2s, intensity=255) +run fire_effect +``` + +## Sequences + +### 7. RGB Show +```berry +color red = 0xFF0000 +color green = 0x00FF00 +color blue = 0x0000FF + +animation red_anim = solid(color=red) +animation green_anim = solid(color=green) +animation blue_anim = solid(color=blue) + +sequence rgb_show { + play red_anim for 2s + play green_anim for 2s + play blue_anim for 2s +} +run rgb_show +``` + +### 8. Sunrise Sequence +```berry +color deep_blue = 0x000080 +color orange = 0xFFA500 +color yellow = 0xFFFF00 + +animation night = solid(color=deep_blue) +animation sunrise = pulsating_animation(color=orange, period=3s) +animation day = solid(color=yellow) + +sequence sunrise_show { + log("Starting sunrise sequence") + play night for 3s + log("Night phase complete, starting sunrise") + play sunrise for 5s + log("Sunrise complete, switching to day") + play day for 3s + log("Sunrise sequence finished") +} +run sunrise_show +``` + +### 8.1. Variable Duration Sequences +```berry +# Define timing variables for consistent durations +set short_duration = 2s +set long_duration = 5s +set fade_time = 1s + +animation red_anim = solid(color=red) +animation green_anim = solid(color=green) +animation blue_anim = solid(color=blue) + +sequence timed_show forever { + play red_anim for short_duration # Use variable duration + wait fade_time # Variable wait time + play green_anim for long_duration # Different variable duration + wait fade_time + play blue_anim for short_duration # Reuse timing variable +} +run timed_show +``` + +## Sequence Assignments + +### 9. Dynamic Property Changes +```berry +# Create oscillators for dynamic position +set triangle_val = triangle(min_value=0, max_value=27, duration=5s) +set cosine_val = cosine_osc(min_value=0, max_value=27, duration=5s) + +# Create color cycle +palette eye_palette = [red, yellow, green, violet] +color eye_color = color_cycle(colors=eye_palette, period=0) + +# Create beacon animation +animation red_eye = beacon_animation( + color=eye_color + pos=cosine_val + beacon_size=3 + slew_size=2 + priority=10 +) + +# Sequence with property assignments +sequence cylon_eye { + play red_eye for 3s + red_eye.pos = triangle_val # Change to triangle oscillator + play red_eye for 3s + red_eye.pos = cosine_val # Change back to cosine + eye_color.next = 1 # Advance to next color +} +run cylon_eye +``` + +### 10. Multiple Assignments in Sequence +```berry +set high_brightness = 255 +set low_brightness = 64 +color my_blue = 0x0000FF + +animation test = solid(color=red) +test.opacity = high_brightness + +sequence demo { + play test for 1s + test.opacity = low_brightness # Dim the animation + test.color = my_blue # Change color to blue + play test for 1s + test.opacity = high_brightness # Brighten again + play test for 1s +} +run demo +``` + +### 11. Restart in Sequences +```berry +# Create oscillator and animation +set wave_osc = triangle(min_value=0, max_value=29, period=4s) +animation wave = beacon_animation(color=blue, pos=wave_osc, beacon_size=5) + +sequence sync_demo { + play wave for 3s + restart wave_osc # Restart oscillator time origin (if already started) + play wave for 3s # Wave starts from beginning again + restart wave # Restart animation time origin (if already started) + play wave for 3s +} +run sync_demo +``` + +### 12. Assignments in Repeat Blocks +```berry +set brightness = smooth(min_value=50, max_value=255, period=2s) +animation pulse = pulsating_animation(color=white, period=1s) + +sequence breathing_cycle { + repeat 3 times { + play pulse for 500ms + pulse.opacity = brightness # Apply breathing effect + wait 200ms + pulse.opacity = 255 # Return to full brightness + } +} +run breathing_cycle +``` + +## User Functions in Computed Parameters + +### 13. Simple User Function +```berry +# Simple user function in computed parameter +animation random_base = solid(color=blue, priority=10) +random_base.opacity = rand_demo() +run random_base +``` + +### 14. User Function with Math Operations +```berry +# Mix user functions with mathematical functions +animation random_bounded = solid( + color=purple + opacity=max(50, min(255, rand_demo() + 100)) + priority=15 +) +run random_bounded +``` + +### 15. User Function in Arithmetic Expression +```berry +# Use user function in arithmetic expressions +animation random_variation = solid( + color=cyan + opacity=abs(rand_demo() - 128) + 64 + priority=12 +) +run random_variation +``` + +See `anim_examples/user_functions_demo.anim` for a complete working example. + +## New Repeat System Examples + +### 16. Runtime Repeat with Forever Loop +```berry +color red = 0xFF0000 +color blue = 0x0000FF +animation red_anim = solid(color=red) +animation blue_anim = solid(color=blue) + +# Traditional syntax with repeat sub-sequence +sequence cylon_effect { + repeat forever { + play red_anim for 1s + play blue_anim for 1s + } +} + +# Alternative syntax - sequence with repeat modifier +sequence cylon_effect_alt repeat forever { + play red_anim for 1s + play blue_anim for 1s +} + +run cylon_effect +``` + +### 17. Nested Repeats (Multiplication) +```berry +color green = 0x00FF00 +color yellow = 0xFFFF00 +animation green_anim = solid(color=green) +animation yellow_anim = solid(color=yellow) + +# Nested repeats: 3 × 2 = 6 total iterations +sequence nested_pattern { + repeat 3 times { + repeat 2 times { + play green_anim for 200ms + play yellow_anim for 200ms + } + wait 500ms # Pause between outer iterations + } +} +run nested_pattern +``` + +### 18. Repeat with Property Assignments +```berry +set triangle_pos = triangle(min_value=0, max_value=29, period=3s) +set cosine_pos = cosine_osc(min_value=0, max_value=29, period=3s) + +color eye_color = color_cycle(colors=[red, yellow, green, blue], period=0) +animation moving_eye = beacon_animation( + color=eye_color + pos=triangle_pos + beacon_size=2 + slew_size=1 +) + +sequence dynamic_cylon { + repeat 5 times { + play moving_eye for 2s + moving_eye.pos = cosine_pos # Switch to cosine movement + play moving_eye for 2s + moving_eye.pos = triangle_pos # Switch back to triangle + eye_color.next = 1 # Next color + } +} +run dynamic_cylon +``` + +## Advanced Examples + +### 19. Dynamic Position +```berry +strip length 60 + +set moving_position = smooth(min_value=5, max_value=55, period=4s) +color purple = 0x8000FF + +animation moving_pulse = beacon_animation( + color=purple, + position=moving_position, + beacon_size=3, + fade_size=2 +) +run moving_pulse +``` + +### 20. Multi-Layer Effect +```berry +# Base layer - slow breathing +set breathing = smooth(min_value=100, max_value=255, period=4s) +color base_blue = 0x000080 +animation base_layer = solid(color=base_blue) +base_layer.opacity = breathing + +# Accent layer - twinkling stars +color star_white = 0xFFFFFF +animation stars = twinkle_animation(color=star_white, count=5, period=800ms) +stars.opacity = 150 + +sequence layered_effect { + play base_layer for 10s + play stars for 10s +} +run layered_effect +``` + +## Tips for Creating Animations + +### Start Simple +```berry +# Begin with basic colors and effects +color my_color = 0xFF0000 +animation simple = solid(color=my_color) +run simple +``` + +### Use Meaningful Names +```berry +# Good - descriptive names +color sunset_orange = 0xFF8C00 +animation evening_glow = pulsating_animation(color=sunset_orange, period=4s) + +# Avoid - unclear names +color c1 = 0xFF8C00 +animation a1 = pulsating_animation(color=c1, period=4s) +``` + +### Test Incrementally +1. Start with solid colors +2. Add simple effects like pulse +3. Experiment with sequences +4. Combine multiple animations + +### Performance Considerations +- Use sequences instead of multiple simultaneous animations +- Reuse value providers with the `set` keyword +- Keep animation periods reasonable (>500ms) +- Limit palette sizes for memory efficiency + +## Template Examples + +Templates provide reusable, parameterized animation patterns that promote code reuse and maintainability. + +### 21. Simple Template +```berry +# Define a reusable blinking template +template blink_effect { + param color type color + param speed + param intensity + + animation blink = pulsating_animation( + color=color + period=speed + ) + blink.opacity = intensity + + run blink +} + +# Use the template with different parameters +blink_effect(red, 1s, 80%) +blink_effect(blue, 500ms, 100%) +``` + +### 22. Multi-Animation Template +```berry +# Template that creates a comet chase effect +template comet_chase { + param trail_color type color + param bg_color type color + param chase_speed + param tail_size + + # Background layer + animation background = solid(color=bg_color) + background.priority = 1 + + # Comet effect layer + animation comet = comet_animation( + color=trail_color + tail_length=tail_size + speed=chase_speed + ) + comet.priority = 10 + + run background + run comet +} + +# Create different comet effects +comet_chase(white, black, 1500ms, 8) +``` + +### 23. Template with Dynamic Colors +```berry +# Template using color cycling and breathing effects +template breathing_rainbow { + param cycle_time + param breath_time + param base_brightness + + # Create rainbow palette + colors rainbow = [ + (0, red), (42, orange), (85, yellow) + (128, green), (170, blue), (213, purple), (255, red) + ] + + # Create cycling rainbow color + color rainbow_cycle = color_cycle( + colors=rainbow + period=cycle_time + ) + + # Create breathing animation with rainbow colors + animation breath = pulsating_animation( + color=rainbow_cycle + period=breath_time + ) + breath.opacity = base_brightness + + run breath +} + +# Use the rainbow breathing template +breathing_rainbow(5s, 2s, 200) +``` + +## Next Steps + +- **[DSL Reference](Dsl_Reference.md)** - Complete language syntax +- **[Troubleshooting](Troubleshooting.md)** - Common issues and solutions +- **[Animation Development](Animation_Development.md)** - Creating custom animations + +Start with these examples and build your own amazing LED animations! 🎨✨ \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Oscillation_Patterns.md b/lib/libesp32/berry_animation/animation_docs/Oscillation_Patterns.md new file mode 100644 index 000000000..3b4a64e08 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Oscillation_Patterns.md @@ -0,0 +1,263 @@ +# Oscillation Patterns + +Quick reference for oscillation patterns used with value providers in the Berry Animation Framework. + +## Available Oscillation Patterns + +These waveform constants can be used with `oscillator_value`: + +| Constant | Value | Alias Functions | Behavior | Use Case | +|----------|-------|-----------------|----------|----------| +| `SAWTOOTH` | 1 | `linear`, `ramp` | Linear ramp up | Uniform motion | +| `TRIANGLE` | 2 | `triangle` | Linear up then down | Sharp direction changes | +| `SQUARE` | 3 | `square` | Alternating min/max | On/off effects | +| `COSINE` | 4 | `smooth` | Smooth cosine wave | Natural oscillation | +| `SINE` | 5 | `sine` | Pure sine wave | Classic wave motion | +| `EASE_IN` | 6 | `ease_in` | Slow start, fast end | Smooth acceleration | +| `EASE_OUT` | 7 | `ease_out` | Fast start, slow end | Smooth deceleration | +| `ELASTIC` | 8 | `elastic` | Spring overshoot | Bouncy effects | +| `BOUNCE` | 9 | `bounce` | Ball bouncing | Physics simulation | + +## DSL Usage + +### With Oscillator Value Provider +```berry +# Basic oscillator with different waveform types +set breathing = oscillator_value(min_value=50, max_value=255, duration=3000, form=COSINE) +set pulsing = ease_in(min_value=0, max_value=255, duration=2000) +set bouncing = oscillator_value(min_value=10, max_value=240, duration=4000, form=TRIANGLE) +``` + +### Using Alias Functions +```berry +# These are equivalent to oscillator_value with specific forms +set smooth_fade = smooth(min_value=50, max_value=255, duration=3000) # form=COSINE +set sine_wave = sine_osc(min_value=50, max_value=255, duration=3000) # form=SINE +set cosine_wave = cosine_osc(min_value=50, max_value=255, duration=3000) # form=COSINE (alias for smooth) +set linear_sweep = linear(min_value=0, max_value=255, duration=2000) # form=SAWTOOTH +set triangle_wave = triangle(min_value=10, max_value=240, duration=4000) # form=TRIANGLE +``` + +### In Animations +```berry +color blue = 0x0000FF +set breathing = smooth(min_value=100, max_value=255, duration=4000) + +animation breathing_blue = solid(color=blue) +breathing_blue.opacity = breathing +run breathing_blue +``` + +## Pattern Characteristics + +### SAWTOOTH (Linear) +- **Constant speed** throughout the cycle +- **Sharp reset** from max back to min +- **Best for**: Uniform sweeps, mechanical movements + +``` +Value + ^ + | /| /| + | / | / | + | / | / | + | / | / | + | / | / | + |/ |/ | + +------+------+----> Time +``` + +```berry +set linear_brightness = linear(min_value=0, max_value=255, duration=2000) +``` + +### COSINE (Smooth) +- **Gradual acceleration** and deceleration +- **Natural feeling** transitions +- **Best for**: Breathing effects, gentle fades + +```berry +set breathing_effect = smooth(min_value=50, max_value=255, duration=3000) +``` + +### SINE (Pure Wave) +- **Classic sine wave** starting from minimum +- **Smooth acceleration** and deceleration like cosine but phase-shifted +- **Best for**: Wave effects, classic oscillations, audio-visual sync + +``` +Value + ^ + | ___ + | / \ + | / \ + | / \ + | / \ + | / \ + | / \ + | / \ + |/ \___ + +--------------------+----> Time +``` + +```berry +set wave_motion = sine_osc(min_value=0, max_value=255, duration=2000) +``` + +### TRIANGLE +- **Linear acceleration** to midpoint, then **linear deceleration** +- **Sharp direction changes** at extremes +- **Best for**: Bouncing effects, sharp transitions + +``` +Value + ^ + | /\ + | / \ + | / \ + | / \ + | / \ + | / \ + |/ \ + +-------------+----> Time +``` + +```berry +set bounce_position = triangle(min_value=5, max_value=55, duration=2000) +``` + +### SQUARE +- **Alternating** between min and max values +- **Instant transitions** with configurable duty cycle +- **Best for**: On/off effects, strobing, digital patterns + +``` +Value + ^ + | +---+ +---+ + | | | | | + | | | | | + | | +-----+ | + | | | + | | | + +-+-------------+----> Time +``` + +```berry +set strobe_effect = square(min_value=0, max_value=255, duration=500, duty_cycle=25) +``` + +### EASE_IN +- **Slow start**, **fast finish** +- **Smooth acceleration** curve +- **Best for**: Starting animations, building intensity + +```berry +set accelerating = ease_in(min_value=0, max_value=255, duration=3000) +``` + +### EASE_OUT +- **Fast start**, **slow finish** +- **Smooth deceleration** curve +- **Best for**: Ending animations, gentle stops + +```berry +set decelerating = ease_out(min_value=255, max_value=0, duration=3000) +``` + +## Value Progression Examples + +For a cycle from 0 to 100 over 2000ms: + +| Time | SAWTOOTH | COSINE | SINE | TRIANGLE | EASE_IN | EASE_OUT | +|------|----------|--------|------|----------|---------|----------| +| 0ms | 0 | 0 | 0 | 0 | 0 | 0 | +| 500ms| 25 | 15 | 50 | 50 | 6 | 44 | +| 1000ms| 50 | 50 | 100 | 100 | 25 | 75 | +| 1500ms| 75 | 85 | 50 | 50 | 56 | 94 | +| 2000ms| 100 | 100 | 0 | 0 | 100 | 100 | + +## Common Patterns + +### Breathing Effect +```berry +color soft_white = 0xC0C0C0 +set breathing = smooth(min_value=80, max_value=255, duration=4000) + +animation breathing_light = solid(color=soft_white) +breathing_light.opacity = breathing +run breathing_light +``` + +### Position Sweep +```berry +strip length 60 +color red = 0xFF0000 +set sweeping_position = linear(min_value=0, max_value=59, duration=3000) + +animation position_sweep = beacon_animation( + color=red, + position=sweeping_position, + beacon_size=3, + fade_size=1 +) +run position_sweep +``` + +### Wave Motion +```berry +color purple = 0x8000FF +set wave_brightness = sine(min_value=50, max_value=255, duration=2500) + +animation wave_effect = solid(color=purple) +wave_effect.opacity = wave_brightness +run wave_effect +``` + +### Bouncing Effect +```berry +color green = 0x00FF00 +set bounce_size = triangle(min_value=1, max_value=8, duration=1000) + +animation bouncing_pulse = beacon_animation( + color=green, + position=30, + beacon_size=bounce_size, + fade_size=1 +) +run bouncing_pulse +``` + +### Accelerating Fade +```berry +color blue = 0x0000FF +set fade_in = ease_in(min_value=0, max_value=255, duration=5000) + +animation accelerating_fade = solid(color=blue) +accelerating_fade.opacity = fade_in +run accelerating_fade +``` + +### Strobe Effect +```berry +color white = 0xFFFFFF +set strobe_pattern = square(min_value=0, max_value=255, duration=200, duty_cycle=10) + +animation strobe_light = solid(color=white) +strobe_light.opacity = strobe_pattern +run strobe_light +``` + +## Tips + +- **COSINE (smooth)**: Most natural for breathing and gentle effects +- **SINE**: Classic wave motion, perfect for audio-visual sync and pure oscillations +- **SAWTOOTH (linear)**: Best for consistent sweeps and mechanical movements +- **TRIANGLE**: Creates sharp, bouncing transitions +- **EASE_IN**: Perfect for building up intensity +- **EASE_OUT**: Ideal for gentle fade-outs +- **ELASTIC**: Spring-like effects with overshoot +- **BOUNCE**: Physics-based bouncing effects +- **SQUARE**: Good for on/off blinking effects + +Choose the oscillation pattern that matches the feeling you want to create in your animation. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Quick_Start.md b/lib/libesp32/berry_animation/animation_docs/Quick_Start.md new file mode 100644 index 000000000..c42551486 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Quick_Start.md @@ -0,0 +1,257 @@ +# Quick Start Guide + +Get up and running with the Berry Animation Framework in 5 minutes using the DSL! + +## Prerequisites + +- Tasmota device with Berry support +- Addressable LED strip (WS2812, SK6812, etc.) + +## Step 1: Your First Animation + +Create a simple pulsing red light: + +```berry +# Define colors +color bordeaux = 0x6F2C4F + +# Create pulsing animation +animation pulse_bordeaux = pulsating_animation(color=bordeaux, period=3s) + +# Run it +run pulse_bordeaux +``` + +## Step 2: Color Cycling + +Create smooth color transitions: + +```berry +# Use predefined rainbow palette +animation rainbow_cycle = rich_palette( + colors=PALETTE_RAINBOW + period=5s + transition_type=1 +) + +run rainbow_cycle +``` + +## Step 3: Custom Palettes + +Create your own color palettes: + +```berry +# Define a sunset palette +palette sunset = [ + (0, 0x191970) # Midnight blue + (64, purple) # Purple + (128, 0xFF69B4) # Hot pink + (192, orange) # Orange + (255, yellow) # Yellow +] + +# Create palette animation +animation sunset_glow = rich_palette( + colors=sunset + period=8s + transition_type=1 +) + +run sunset_glow +``` + +## Step 4: Sequences + +Create complex shows with sequences: + +```berry +animation red_pulse = pulsating_animation(color=red, period=2s) +animation green_pulse = pulsating_animation(color=green, period=2s) +animation blue_pulse = pulsating_animation(color=blue, period=2s) + +sequence rgb_show { + play red_pulse for 3s + wait 500ms + play green_pulse for 3s + wait 500ms + play blue_pulse for 3s + + repeat 2 times { + play red_pulse for 1s + play green_pulse for 1s + play blue_pulse for 1s + } +} + +run rgb_show +``` + +**Pro Tip: Variable Durations** +Use variables for consistent timing: + +```berry +# Define timing variables +set short_time = 1s +set long_time = 3s + +sequence timed_show { + play red_pulse for long_time # Use variable duration + wait 500ms + play green_pulse for short_time # Different timing + play blue_pulse for long_time # Reuse timing +} +``` + +## Step 5: Dynamic Effects + +Add movement and variation to your animations: + +```berry +# Breathing effect with smooth oscillation +animation breathing = pulsating_animation( + color=blue + min_brightness=20% + max_brightness=100% + period=4s +) + +# Moving comet effect +animation comet = comet_animation( + color=white + tail_length=8 + speed=2000 +) + +# Twinkling effect +animation sparkles = twinkle_animation( + color=white + count=8 + period=800ms +) + +run breathing +``` + +## Common Patterns + +### Fire Effect +```berry +animation fire = rich_palette( + colors=PALETTE_FIRE + period=2s + transition_type=1 +) + +run fire +``` + +## Loading DSL Files + +Save your DSL code in `.anim` files and load them: + +```berry +import animation + +# Load DSL file +var runtime = animation.load_dsl_file("my_animation.anim") +``` + +## Templates - Reusable Animation Patterns + +### Template Animations + +Template animations create reusable animation classes with parameters: + +```berry +# Define a template animation with constraints +template animation shutter_effect { + param colors type palette nillable true + param duration type time min 0 max 3600 default 5 nillable false + + set strip_len = strip_length() + color col = color_cycle(colors=colors, period=0) + + animation shutter = beacon_animation( + color = col + beacon_size = strip_len / 2 + ) + + sequence seq repeat forever { + play shutter for duration + col.next = 1 + } + + run seq +} + +# Create multiple instances with different parameters +palette rainbow = [red, orange, yellow, green, blue] +animation shutter1 = shutter_effect(colors=rainbow, duration=2s) +animation shutter2 = shutter_effect(colors=rainbow, duration=5s) + +run shutter1 +run shutter2 +``` + +**Template Animation Features:** +- **Reusable Classes** - Create multiple instances with different parameters +- **Parameter Constraints** - min, max, default, nillable values +- **Composition** - Combine multiple animations and sequences +- **Type Safe** - Parameter type checking +- **Implicit Parameters** - Automatically inherit parameters from base classes (name, priority, duration, loop, opacity, color, is_running) + +### Regular Templates + +Regular templates generate functions for simpler use cases: + +```berry +template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse +} + +# Use the template +pulse_effect(red, 2s) +pulse_effect(blue, 1s) +``` + +## User-Defined Functions (Advanced) + +For complex logic, create custom functions in Berry: + +```berry +# Define custom function - engine must be first parameter +def my_twinkle(engine, color, count, period) + var anim = animation.twinkle_animation(engine) + anim.color = color + anim.count = count + anim.period = period + return anim +end + +# Register for DSL use +animation.register_user_function("twinkle", my_twinkle) +``` + +```berry +# Use in DSL - engine is automatically passed +animation gold_twinkles = twinkle(0xFFD700, 8, 500ms) +run gold_twinkles +``` + +**Note**: The DSL automatically passes `engine` as the first argument to user functions. + +## Next Steps + +- **[DSL Reference](Dsl_Reference.md)** - Complete DSL syntax and features +- **[User Functions](User_Functions.md)** - Create custom animation functions +- **[Examples](Examples.md)** - More complex animation examples +- **[Animation Class Hierarchy](Animation_Class_Hierarchy.md)** - All available animations and parameters +- **[Oscillation Patterns](Oscillation_Patterns.md)** - Dynamic value patterns +- **[Troubleshooting](Troubleshooting.md)** - Common issues and solutions + +Happy animating! 🎨✨ \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Transpiler_Architecture.md b/lib/libesp32/berry_animation/animation_docs/Transpiler_Architecture.md new file mode 100644 index 000000000..7ca28eed2 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Transpiler_Architecture.md @@ -0,0 +1,858 @@ +# DSL Transpiler Architecture + +This document provides a detailed overview of the Berry Animation DSL transpiler architecture, including the core processing flow and expression processing chain. + +## Overview + +The DSL transpiler (`transpiler.be`) converts Animation DSL code into executable Berry code. It uses a **ultra-simplified single-pass architecture** with comprehensive validation and code generation capabilities. The refactored transpiler emphasizes simplicity, robustness, and maintainability while providing extensive compile-time validation. + +### Single-Pass Architecture Clarification + +The transpiler is truly **single-pass** - it processes the token stream once from start to finish. When the documentation mentions "sequential steps" (like in template processing), these refer to **sequential operations within the single pass**, not separate passes over the data. For example: + +- Template processing collects parameters, then collects body tokens **sequentially** in one pass +- Expression transformation handles mathematical functions, then user variables **sequentially** in one operation +- The transpiler never backtracks or re-processes the same tokens multiple times + +## Core Processing Flow + +The transpiler follows an **ultra-simplified single-pass architecture** with the following main flow: + +``` +transpile() +├── add("import animation") +├── while !at_end() +│ └── process_statement() +│ ├── Handle comments (preserve in output) +│ ├── Skip whitespace/newlines +│ ├── Auto-initialize strip if needed +│ ├── process_color() +│ │ ├── validate_user_name() +│ │ ├── _validate_color_provider_factory_exists() +│ │ └── _process_named_arguments_for_color_provider() +│ ├── process_palette() +│ │ ├── validate_user_name() +│ │ ├── Detect tuple vs alternative syntax +│ │ └── process_palette_color() (strict validation) +│ ├── process_animation() +│ │ ├── validate_user_name() +│ │ ├── _validate_animation_factory_creates_animation() +│ │ └── _process_named_arguments_for_animation() +│ ├── process_set() +│ │ ├── validate_user_name() +│ │ └── process_value() +│ ├── process_template() +│ │ ├── validate_user_name() +│ │ ├── Collect parameters with type annotations +│ │ ├── Collect body tokens +│ │ └── generate_template_function() +│ ├── process_sequence() +│ │ ├── validate_user_name() +│ │ ├── Parse repeat syntax (multiple variants) +│ │ └── process_sequence_statement() (fluent interface) +│ │ ├── process_play_statement_fluent() +│ │ ├── process_wait_statement_fluent() +│ │ ├── process_log_statement_fluent() +│ │ ├── process_restart_statement_fluent() +│ │ └── process_sequence_assignment_fluent() +│ ├── process_import() (direct Berry import generation) +│ ├── process_event_handler() (basic event system support) +│ ├── process_berry_code_block() (embed arbitrary Berry code) +│ ├── process_run() (collect for single engine.run()) +│ └── process_property_assignment() +└── generate_engine_start() (single call for all run statements) +``` + +### Statement Processing Details + +#### Color Processing +``` +process_color() +├── expect_identifier() → color name +├── validate_user_name() → check against reserved names +├── expect_assign() → '=' +├── Check if function call (color provider) +│ ├── Check template_definitions first +│ ├── _validate_color_provider_factory_exists() +│ ├── add("var name_ = animation.func(engine)") +│ ├── Track in symbol_table for validation +│ └── _process_named_arguments_for_color_provider() +└── OR process_value() → static color value with symbol tracking +``` + +#### Animation Processing +``` +process_animation() +├── expect_identifier() → animation name +├── validate_user_name() → check against reserved names +├── expect_assign() → '=' +├── Check if function call (animation factory) +│ ├── Check template_definitions first +│ ├── _validate_animation_factory_creates_animation() +│ ├── add("var name_ = animation.func(engine)") +│ ├── Track in symbol_table for validation +│ └── _process_named_arguments_for_animation() +└── OR process_value() → reference or literal with symbol tracking +``` + +#### Sequence Processing (Enhanced) +``` +process_sequence() +├── expect_identifier() → sequence name +├── validate_user_name() → check against reserved names +├── Track in sequence_names and symbol_table +├── Parse multiple repeat syntaxes: +│ ├── "sequence name repeat N times { ... }" +│ ├── "sequence name forever { ... }" +│ ├── "sequence name N times { ... }" +│ └── "sequence name { repeat ... }" +├── expect_left_brace() → '{' +├── add("var name_ = animation.sequence_manager(engine, repeat_count)") +├── while !check_right_brace() +│ └── process_sequence_statement() (fluent interface) +└── expect_right_brace() → '}' +``` + +#### Template Processing +``` +process_template() +├── expect_identifier() → template name +├── validate_user_name() → check against reserved names +├── expect_left_brace() → '{' +├── Sequential step 1: collect parameters with type annotations +├── Sequential step 2: collect body tokens +├── expect_right_brace() → '}' +├── Store in template_definitions +├── generate_template_function() +│ ├── Create new transpiler instance for body +│ ├── Transpile body with fresh symbol table +│ ├── Generate Berry function with engine parameter +│ └── Register as user function +└── Track in symbol_table as "template" + +process_template_animation() +├── expect_identifier() → template animation name +├── validate_user_name() → check against reserved names +├── expect_left_brace() → '{' +├── Sequential step 1: collect parameters with constraints (type, min, max, default) +├── Sequential step 2: collect body tokens +├── expect_right_brace() → '}' +├── generate_template_animation_class() +│ ├── Generate class extending engine_proxy +│ ├── Generate PARAMS with encode_constraints +│ ├── Create new transpiler instance for body +│ ├── Set template_animation_params for special handling +│ │ ├── Add user-defined parameters +│ │ └── Add inherited parameters from engine_proxy hierarchy (dynamic discovery) +│ ├── Transpile body with self.param references +│ └── Use self.add() instead of engine.add() +└── Track in symbol_table as "template" + +### Implicit Parameters in Template Animations + +Template animations automatically inherit parameters from the `engine_proxy` class hierarchy. The transpiler dynamically discovers these parameters at compile time: + +**Dynamic Parameter Discovery:** +``` +_add_inherited_params_to_template(template_params_map) +├── Create temporary engine_proxy instance +├── Walk up class hierarchy using introspection +├── For each class with PARAMS: +│ └── Add all parameter names to template_params_map +└── Fallback to static list if instance creation fails +``` + +**Inherited Parameters (from Animation and ParameterizedObject):** +- `id` (string, default: "animation") +- `priority` (int, default: 10) +- `duration` (int, default: 0) +- `loop` (bool, default: false) +- `opacity` (int, default: 255) +- `color` (int, default: 0) +- `is_running` (bool, default: false) + +**Parameter Resolution Order:** +1. Check if identifier is in `template_animation_params` (includes both user-defined and inherited) +2. If found, resolve as `self.` (template animation parameter) +3. Otherwise, check symbol table for user-defined variables +4. If not found, raise "Unknown identifier" error + +This allows template animations to use inherited parameters like `duration` and `opacity` without explicit declaration, while still maintaining type safety and validation. +``` + +## Expression Processing Chain + +The transpiler uses a **unified recursive descent parser** for expressions with **raw mode support** for closure contexts: + +``` +process_value(context) +└── process_additive_expression(context, is_top_level=true, raw_mode=false) + ├── process_multiplicative_expression(context, is_top_level, raw_mode) + │ ├── process_unary_expression(context, is_top_level, raw_mode) + │ │ └── process_primary_expression(context, is_top_level, raw_mode) + │ │ ├── Parenthesized expression → recursive call + │ │ ├── Function call handling: + │ │ │ ├── Raw mode: mathematical functions → animation._math.method() + │ │ │ ├── Raw mode: template calls → template_func(self.engine, ...) + │ │ │ ├── Regular mode: process_function_call() or process_nested_function_call() + │ │ │ └── Simple function detection → _is_simple_function_call() + │ │ ├── Color literal → convert_color() (enhanced ARGB support) + │ │ ├── Time literal → process_time_value() (with variable support) + │ │ ├── Percentage → process_percentage_value() + │ │ ├── Number literal → return as-is + │ │ ├── String literal → quote and return + │ │ ├── Array literal → process_array_literal() (not in raw mode) + │ │ ├── Identifier → enhanced symbol resolution + │ │ │ ├── Object property → "obj.prop" with validation + │ │ │ ├── User function → _process_user_function_call() + │ │ │ ├── Palette constant → "animation.PALETTE_RAINBOW" etc. + │ │ │ ├── Named color → get_named_color_value() + │ │ │ └── Consolidated symbol resolution → resolve_symbol_reference() + │ │ └── Boolean keywords → true/false + │ └── Handle unary operators (-, +) + └── Handle multiplicative operators (*, /) +└── Handle additive operators (+, -) +└── Closure wrapping logic: + ├── Skip in raw_mode + ├── Special handling for repeat_count context + ├── is_computed_expression_string() detection + └── create_computation_closure_from_string() +``` + +### Expression Context Handling + +The expression processor handles different contexts with **enhanced validation and processing**: + +- **`"color"`** - Color definitions and assignments +- **`"animation"`** - Animation definitions and assignments +- **`"argument"`** - Function call arguments +- **`"property"`** - Property assignments with validation +- **`"variable"`** - Variable assignments with type tracking +- **`"repeat_count"`** - Sequence repeat counts (special closure handling) +- **`"time"`** - Time value processing with variable support +- **`"array_element"`** - Array literal elements +- **`"event_param"`** - Event handler parameters +- **`"expression"`** - Raw expression context (for closures) + +### Computed Expression Detection (Enhanced) + +The transpiler automatically detects computed expressions that need closures with **improved accuracy**: + +``` +is_computed_expression_string(expr_str) +├── Check for arithmetic operators (+, -, *, /) with spaces +├── Check for function calls (excluding simple functions) +│ ├── Extract function name before parenthesis +│ ├── Use _is_simple_function_call() to filter +│ └── Only mark complex functions as needing closures +├── Exclude simple parenthesized literals like (-1) +└── Return true only for actual computations + +create_computation_closure_from_string(expr_str) +├── transform_expression_for_closure() +│ ├── Sequential step 1: Transform mathematical functions → animation._math.method() +│ │ ├── Use dynamic introspection with is_math_method() +│ │ ├── Check for existing "self." prefix /// TODO NOT SURE IT STILL EXISTS +│ │ └── Only transform if not already prefixed +│ ├── Sequential step 2: Transform user variables → animation.resolve(var_) +│ │ ├── Find variables ending with _ +│ │ ├── Check for existing resolve() calls +│ │ ├── Avoid double-wrapping +│ │ └── Handle identifier character boundaries +│ └── Clean up extra spaces +└── Return "animation.create_closure_value(engine, closure)" + +is_anonymous_function(expr_str) +├── Check if expression starts with "(def " +├── Check if expression ends with ")(engine)" +└── Skip closure wrapping for already-wrapped functions +``` + +## Enhanced Symbol Table System + +The transpiler uses a sophisticated **SymbolTable** system for holistic symbol management and caching. This system provides dynamic symbol detection, type validation, and conflict prevention. + +### SymbolTable Architecture + +The symbol table consists of two main classes in `symbol_table.be`: + +#### SymbolEntry Class +``` +SymbolEntry +├── name: string # Symbol name +├── type: string # Symbol type classification +├── instance: object # Actual instance for validation +├── takes_args: boolean # Whether symbol accepts arguments +├── arg_type: string # "positional", "named", or "none" +└── is_builtin: boolean # Whether this is a built-in symbol from animation module +``` + +**Symbol Types Supported:** +- `"palette"` - Palette objects like `PALETTE_RAINBOW` (bytes instances) +- `"constant"` - Integer constants like `LINEAR`, `SINE`, `COSINE` +- `"math_function"` - Mathematical functions like `max`, `min` +- `"user_function"` - User-defined functions registered at runtime +- `"value_provider"` - Value provider constructors +- `"animation"` - Animation constructors +- `"color"` - Color definitions and providers +- `"variable"` - User-defined variables +- `"sequence"` - Sequence definitions +- `"template"` - Template definitions + +#### SymbolTable Class +``` +SymbolTable +├── entries: map # Map of name -> SymbolEntry +├── mock_engine: MockEngine # For validation testing +├── Dynamic Detection Methods: +│ ├── _detect_and_cache_symbol() # On-demand symbol detection +│ ├── contains() # Existence check with auto-detection +│ └── get() # Retrieval with auto-detection +├── Creation Methods: +│ ├── create_palette() +│ ├── create_color() +│ ├── create_animation() +│ ├── create_value_provider() +│ ├── create_variable() +│ ├── create_sequence() +│ └── create_template() +└── Validation Methods: + ├── symbol_exists() + ├── get_reference() + └── takes_args() / takes_positional_args() / takes_named_args() +``` + +### Dynamic Symbol Detection + +The SymbolTable uses **lazy detection** to identify and cache symbols as they are encountered: + +``` +_detect_and_cache_symbol(name) +├── Check if already cached → return cached entry +├── Check animation module using introspection: +│ ├── Detect bytes() instances → create_palette() +│ ├── Detect integer constants (type == "int") → create_constant() +│ ├── Detect math functions in animation._math → create_math_function() +│ ├── Detect user functions via animation.is_user_function() → create_user_function() +│ ├── Test constructors with MockEngine: +│ │ ├── Create instance with mock_engine +│ │ ├── Check isinstance(instance, animation.value_provider) → create_value_provider() +│ │ └── Check isinstance(instance, animation.animation) → create_animation() +│ └── Cache result for future lookups +└── Return nil if not found (handled as user-defined) +``` + +### Symbol Type Detection Examples + +**Palette Detection:** +```berry +# DSL: animation rainbow = rich_palette_animation(colors=PALETTE_RAINBOW) +# Detection: PALETTE_RAINBOW exists in animation module, isinstance(obj, bytes) +# Result: SymbolEntry("PALETTE_RAINBOW", "palette", bytes_instance, true) +# Reference: "animation.PALETTE_RAINBOW" +``` + +**Constant Detection:** +```berry +# DSL: animation wave = wave_animation(waveform=LINEAR) +# Detection: LINEAR exists in animation module, type(LINEAR) == "int" +# Result: SymbolEntry("LINEAR", "constant", 1, true) +# Reference: "animation.LINEAR" +``` + +**Math Function Detection:** +```berry +# DSL: animation.opacity = max(100, min(255, brightness)) +# Detection: max exists in animation._math, is callable +# Result: SymbolEntry("max", "math_function", nil, true) +# Reference: "animation.max" (transformed to "animation._math.max" in closures) +``` + +**Value Provider Detection:** +```berry +# DSL: set oscillator = triangle(min_value=0, max_value=100, period=2s) +# Detection: triangle(mock_engine) creates instance, isinstance(instance, animation.value_provider) +# Result: SymbolEntry("triangle", "value_provider", instance, true) +# Reference: "animation.triangle" +``` + +**User Function Detection:** +```berry +# DSL: animation demo = rand_demo(color=red) +# Detection: animation.is_user_function("rand_demo") returns true +# Result: SymbolEntry("rand_demo", "user_function", nil, true) +# Reference: "rand_demo_" (handled specially in function calls) +``` + +### Symbol Conflict Prevention + +The SymbolTable prevents symbol redefinition conflicts: + +``` +add(name, entry) +├── Check for built-in symbol conflicts: +│ ├── _detect_and_cache_symbol(name) +│ └── Raise "symbol_redefinition_error" if types differ +├── Check existing user-defined symbols: +│ ├── Compare entry.type with existing.type +│ └── Raise "symbol_redefinition_error" if types differ +├── Allow same-type updates (reassignment) +└── Return entry for method chaining +``` + +**Example Conflict Detection:** +```berry +# This would raise an error: +color max = 0xFF0000 # Conflicts with built-in math function "max" + +# This would also raise an error: +color red = 0xFF0000 +animation red = solid(color=blue) # Redefining "red" as different type +``` + +### Integration with Transpiler + +The SymbolTable integrates seamlessly with the transpiler's processing flow: + +### Performance Optimizations + +**Caching Strategy:** +- **Lazy Detection**: Symbols detected only when first encountered +- **Instance Reuse**: MockEngine instances reused for validation +- **Introspection Caching**: Built-in symbol detection cached permanently + +**Memory Efficiency:** +- **Minimal Storage**: Only essential information stored per symbol +- **Shared MockEngine**: Single MockEngine instance for all validation +- **Reference Counting**: Automatic cleanup of unused entries + +### MockEngine Integration + +The SymbolTable uses a lightweight MockEngine for constructor validation: + +``` +MockEngine +├── time_ms: 0 # Mock time for validation +├── get_strip_length(): 30 # Default strip length +└── Minimal interface for instance creation testing +``` + +**Usage in Detection:** +```berry +# Test if function creates value provider +try + var instance = factory_func(self.mock_engine) + if isinstance(instance, animation.value_provider) + return SymbolEntry.create_value_provider(name, instance, animation.value_provider) + end +except .. as e, msg + # Constructor failed - not a valid provider +end +``` + +## Validation System (Comprehensive) + +The transpiler includes **extensive compile-time validation** with robust error handling: + +### Factory Function Validation (Simplified using SymbolTable) +``` +_validate_animation_factory_exists(func_name) +├── Skip validation for mathematical functions +├── Use symbol_table.get(func_name) for dynamic detection +└── Return true if entry exists (any callable function is valid) + +_validate_animation_factory_creates_animation(func_name) +├── Use symbol_table.get(func_name) for dynamic detection +└── Return true if entry.type == "animation" + +_validate_color_provider_factory_onsts(func_name) +├── Use symbol_table.get(func_name) for dynamic detection +└── Return true if entry exists (any callable function is valid) + +_validate_value_provider_factory_exists(func_name) +├── Use symbol_table.get(func_name) for dynamic detection +└── Return true if entry.type == "value_provider" +``` + +### Parameter Validation (Real-time) +``` +_validate_single_parameter(func_name, param_name, animation_instance) +├── Use introspection to check if parameter exists +├── Call instance.has_param(param_name) for validation +├── Report detailed error messages with line numbers +├── Validate immediately as parameters are parsed +└── Graceful error handling to ensure transpiler robustness + +_create_instance_for_validation(func_name) - Simplified using SymbolTable +├── Use symbol_table.get(func_name) for dynamic detection +└── Return entry.instance if available, nil otherwise +``` + +### Reference Validation (Simplified using SymbolTable) +``` +resolve_symbol_reference(name) - Simplified using SymbolTable +└── Use symbol_table.get_reference(name) for all symbol resolution + +validate_symbol_reference(name, context) - With error reporting +├── Use symbol_exists() to check symbol_table +├── Report detailed error with context information +└── Return validation status + +symbol_exists(name) - Simplified existence check +└── Use symbol_table.symbol_exists(name) for unified checking + +_validate_value_provider_reference(object_name, context) - Simplified +├── Check symbol_exists() using symbol_table +├── Use symbol_table.get(name) for type information +├── Check entry.type == "value_provider" || entry.type == "animation" +└── Report detailed error messages for invalid types +``` + +### User Name Validation (Reserved Names) +``` +validate_user_name(name, definition_type) +├── Check against predefined color names +├── Check against DSL statement keywords +├── Report conflicts with suggestions for alternatives +└── Prevent redefinition of reserved identifiers +``` + +### Value Provider Validation (New) +``` +_validate_value_provider_reference(object_name, context) +├── Check if symbol exists using validate_symbol_reference() +├── Check symbol_table markers for type information +├── Validate instance types using isinstance() +├── Ensure only value providers/animations can be restarted +└── Provide detailed error messages for invalid types +``` + +## Code Generation Patterns + +### Engine-First Pattern (Consistent) +All factory functions use the engine-first pattern with **automatic strip initialization**: +```berry +# DSL: animation pulse = pulsating_animation(color=red, period=2s) +# Generated: +# Auto-generated strip initialization (using Tasmota configuration) +var engine = animation.init_strip() + +var pulse_ = animation.pulsating_animation(engine) +pulse_.color = animation.red +pulse_.period = 2000 +``` + +**Template-Only Exception**: Files containing only template definitions skip engine initialization and `engine.run()` generation, producing pure function libraries. + +### Symbol Resolution (Consolidated) +The transpiler resolves symbols at compile time using **unified resolution logic** based on the `is_builtin` flag: +```berry +# Built-in symbols (is_builtin=true) from animation module → animation.symbol +animation.linear, animation.PALETTE_RAINBOW, animation.SINE, animation.solid + +# User-defined symbols (is_builtin=false) → symbol_ +my_color_, my_animation_, my_sequence_ + +# Named colors → direct ARGB values (resolved at compile time) +red → 0xFFFF0000, blue → 0xFF0000FF + +# Template calls → template_function(engine, args) +my_template(red, 2s) → my_template_template(engine, 0xFFFF0000, 2000) + + +### Closure Generation (Enhanced) +Dynamic expressions are wrapped in closures with **mathematical function support**: +```berry +# DSL: animation.opacity = strip_length() / 2 + 50 +# Generated: +animation.opacity = animation.create_closure_value(engine, + def (self) return animation.resolve(strip_length_(engine)) / 2 + 50 end) + +# DSL: animation.opacity = max(100, min(255, rand_demo() + 50)) +# Generated: +animation.opacity = animation.create_closure_value(engine, + def (self) return animation._math.max(100, animation._math.min(255, animation.get_user_function('rand_demo')(engine) + 50)) end) + +# Mathematical functions are automatically detected and prefixed with animation._math. +# User functions are wrapped with animation.get_user_function() calls +``` + +### Template Generation (New) +Templates are transpiled into Berry functions and registered as user functions: +```berry +# DSL Template: +template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse +} + +# Generated: +def pulse_effect_template(engine, color_, speed_) + var pulse_ = animation.pulsating_animation(engine) + pulse_.color = color_ + pulse_.period = speed_ + engine.add(pulse_) +end + +animation.register_user_function('pulse_effect', pulse_effect_template) +``` + +### Sequence Generation (Fluent Interface) +Sequences use fluent interface pattern for better readability: +```berry +# DSL: sequence demo { play anim for 2s; wait 1s } +# Generated: +var demo_ = animation.sequence_manager(engine) + .push_play_step(anim_, 2000) + .push_wait_step(1000) + +# Nested repeats use sub-sequences: +var demo_ = animation.sequence_manager(engine) + .push_repeat_subsequence(animation.sequence_manager(engine, 3) + .push_play_step(anim_, 1000) + ) +``` + +## Template System (Enhanced) + +Templates are transpiled into Berry functions with **comprehensive parameter handling**: + +**Template-Only Optimization**: Files containing only template definitions skip engine initialization and execution code generation, producing pure Berry function libraries. + +``` +process_template() +├── expect_identifier() → template name +├── validate_user_name() → check against reserved names +├── expect_left_brace() → '{' +├── Sequential step 1: collect parameters with type annotations +│ ├── Parse "param name type annotation" syntax +│ ├── Store parameter names and optional types +│ └── Support both typed and untyped parameters +├── Sequential step 2: collect body tokens until closing brace +│ ├── Handle nested braces correctly +│ ├── Preserve all tokens for later transpilation +│ └── Track brace depth for proper parsing +├── expect_right_brace() → '}' +├── Store in template_definitions for call resolution +├── generate_template_function() +│ ├── Create new SimpleDSLTranspiler instance for body +│ ├── Set up fresh symbol table with parameters +│ ├── Mark strip as initialized (templates assume engine exists) +│ ├── Transpile body using transpile_template_body() +│ ├── Generate Berry function with engine + parameters +│ ├── Handle transpilation errors gracefully +│ └── Register as user function automatically +└── Track in symbol_table as "template" +``` + +### Template Call Resolution (Multiple Contexts) +```berry +# DSL template call in animation context: +animation my_anim = my_template(red, 2s) +# Generated: var my_anim_ = my_template_template(engine, 0xFFFF0000, 2000) + +# DSL template call in property context: +animation.opacity = my_template(blue, 1s) +# Generated: animation.opacity = my_template_template(self.engine, 0xFF0000FF, 1000) + +# DSL standalone template call: +my_template(green, 3s) +# Generated: my_template_template(engine, 0xFF008000, 3000) +``` + +### Template Body Transpilation +Templates use a **separate transpiler instance** with isolated symbol table: +- Fresh symbol table prevents name conflicts +- Parameters are added as "parameter" markers +- Run statements are processed immediately (not collected) +- Template calls can be nested (templates calling other templates) +- Error handling preserves context information + +## Error Handling (Robust) + +The transpiler provides **comprehensive error reporting** with graceful degradation: + +### Error Categories +- **Syntax errors** - Invalid DSL syntax with line numbers +- **Factory validation** - Non-existent animation/color factories with suggestions +- **Parameter validation** - Invalid parameter names with class context +- **Reference validation** - Undefined object references with context information +- **Constraint validation** - Parameter values outside valid ranges +- **Type validation** - Incorrect parameter types with expected types +- **Safety validation** - Dangerous patterns that could cause memory leaks or performance issues +- **Template errors** - Template definition and call validation +- **Reserved name conflicts** - User names conflicting with built-ins + +### Error Reporting Features +```berry +error(msg) +├── Capture current line number from token +├── Format error with context: "Line X: message" +├── Store in errors array for batch reporting +└── Continue transpilation for additional error discovery + +get_error_report() +├── Check if errors exist +├── Format comprehensive error report +├── Include all errors with line numbers +└── Provide user-friendly error messages +``` + +### Graceful Error Handling +- **Try-catch blocks** around validation to prevent crashes +- **Robust validation** that continues on individual failures +- **Skip statement** functionality to recover from parse errors +- **Default values** when validation fails to maintain transpilation flow +- **Context preservation** in error messages for better debugging + +## Performance Considerations + +### Ultra-Simplified Architecture +- **Single-pass processing** - tokens processed once from start to finish +- **Incremental symbol table** - builds validation context as it parses +- **Immediate validation** - catches errors as soon as they're encountered +- **Minimal state tracking** - only essential information is maintained + +### Compile-Time Optimization +- **Symbol resolution at transpile time** - eliminates runtime lookups +- **Parameter validation during parsing** - catches errors early +- **Template pre-compilation** - templates become efficient Berry functions +- **Closure detection** - only wraps expressions that actually need it +- **Mathematical function detection** - uses dynamic introspection for accuracy + +### Memory Efficiency +- **Streaming token processing** - no large intermediate AST structures +- **Direct code generation** - output generated as parsing proceeds +- **Minimal intermediate representations** - tokens and symbol table only +- **Template isolation** - separate transpiler instances prevent memory leaks +- **Graceful error handling** - prevents memory issues from validation failures + +### Validation Efficiency +- **MockEngine pattern** - lightweight validation without full engine +- **Introspection caching** - validation results can be cached +- **Early termination** - stops processing invalid constructs quickly +- **Batch error reporting** - collects multiple errors in single pass + +## Integration Points + +### Animation Module Integration +- **Factory function discovery** via introspection with existence checking +- **Parameter validation** using instance methods and has_param() +- **Symbol resolution** using module contents with fallback handling +- **Mathematical function detection** using dynamic introspection of ClosureValueProvider +- **Automatic strip initialization** when no explicit strip configuration + +### User Function Integration +- **Template registration** as user functions with automatic naming +- **User function call detection** usable as normal functions with positional arguments +- **Closure generation** for computed parameters with mathematical functions +- **Template call resolution** in multiple contexts (animation, property, standalone) +- **Import statement processing** for user function modules + +### DSL Language Integration +- **Comment preservation** in generated Berry code +- **Inline comment handling** with proper spacing +- **Multiple syntax support** for sequences (repeat variants) +- **Palette syntax flexibility** (tuple vs alternative syntax) +- **Time unit conversion** with variable support +- **Percentage conversion** to 0-255 range + +### Robustness Features +- **Graceful error recovery** - continues parsing after errors +- **Validation isolation** - validation failures don't crash transpiler +- **Symbol table tracking** - maintains context for validation +- **Template isolation** - separate transpiler instances prevent conflicts +- **Reserved name protection** - prevents conflicts with built-in identifiers + +## Key Architectural Changes + +The refactored transpiler emphasizes: + +1. **Simplicity** - Ultra-simplified single-pass architecture +2. **Robustness** - Comprehensive error handling and graceful degradation +3. **Enhanced Symbol Management** - Dynamic SymbolTable system with intelligent caching and conflict detection +4. **Validation** - Extensive compile-time validation with detailed error messages +5. **Flexibility** - Support for templates, multiple syntax variants, and user functions +6. **Performance** - Efficient processing with minimal memory overhead and lazy symbol detection +7. **Maintainability** - Clear separation of concerns and unified processing methods + +## Recent Refactoring Improvements + +### Code Simplification Using SymbolTable + +The transpiler has been significantly refactored to leverage the `symbol_table.be` system more extensively: + +#### **Factory Validation Simplification** +- **Before**: Complex validation with introspection and manual instance creation (~50 lines) +- **After**: Simple validation using symbol_table's dynamic detection (~25 lines) +- **Improvement**: 50% code reduction with better maintainability + +#### **Symbol Resolution Consolidation** +- **Before**: Multiple separate checks for sequences, introspection, etc. +- **After**: Unified resolution through `symbol_table.get_reference()` +- **Improvement**: Single source of truth for all symbol resolution + +#### **Duplicate Code Elimination** +- **Before**: Duplicate code patterns in `process_color()` and `process_animation()` methods +- **After**: Consolidated into reusable `_process_simple_value_assignment()` helper +- **Improvement**: 70% reduction in duplicate code blocks + +#### **Legacy Variable Removal** +- **Before**: Separate tracking of sequences in `sequence_names` variable +- **After**: All symbols tracked uniformly in `symbol_table` +- **Improvement**: Eliminated redundancy and simplified state management + +### Major Enhancements + +**SymbolTable System:** +- **Dynamic Detection**: Automatically detects and caches symbol types as encountered +- **Conflict Prevention**: Prevents redefinition of symbols with different types +- **Performance Optimization**: Lazy loading and efficient symbol resolution for optimal performance +- **Type Safety**: Comprehensive type checking with MockEngine validation +- **Modular Design**: Separated into `symbol_table.be` for reusability +- **Constant Detection**: Added support for integer constants like `LINEAR`, `SINE`, `COSINE` + +**Enhanced Symbol Detection:** +- **Palette Objects**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW` +- **Integer Constants**: `LINEAR`, `SINE`, `COSINE` → `animation.LINEAR`, `animation.SINE`, `animation.COSINE` +- **Math Functions**: `max`, `min` → `animation.max`, `animation.min` (transformed to `animation._math.*` in closures) +- **Value Providers**: `triangle`, `smooth` → `animation.triangle`, `animation.smooth` +- **Animation Constructors**: `solid`, `pulsating_animation` → `animation.solid`, `animation.pulsating_animation` +- **User-defined Symbols**: `my_color`, `my_animation` → `my_color_`, `my_animation_` + +**Validation Improvements:** +- **Real-time Validation**: Parameter validation as symbols are parsed +- **Instance-based Checking**: Uses actual instances for accurate validation +- **Graceful Error Handling**: Robust error recovery with detailed error messages +- **Simplified Validation Methods**: Factory validation reduced from ~50 to ~25 lines using symbol_table +- **Unified Symbol Checking**: All symbol existence checks go through symbol_table system +- **Enhanced Type Detection**: Automatic detection of constants, palettes, functions, and constructors + +This architecture ensures robust, efficient transpilation from DSL to executable Berry code while providing comprehensive validation, detailed error reporting, intelligent symbol management, and extensive language features. + +### Symbol Reference Generation + +The enhanced SymbolEntry system uses the `is_builtin` flag to determine correct reference generation: + +```berry +# SymbolEntry.get_reference() method +def get_reference() + if self.is_builtin + return f"animation.{self.name}" # Built-in symbols: animation.LINEAR + else + return f"{self.name}_" # User-defined symbols: my_color_ + end +end +``` + +**Examples:** +- **Built-in Constants**: `LINEAR` → `animation.LINEAR` +- **Built-in Functions**: `triangle` → `animation.triangle` +- **Built-in Palettes**: `PALETTE_RAINBOW` → `animation.PALETTE_RAINBOW` +- **User-defined Colors**: `my_red` → `my_red_` +- **User-defined Animations**: `pulse_anim` → `pulse_anim_` + +This ensures consistent and correct symbol resolution throughout the transpilation process. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/Troubleshooting.md b/lib/libesp32/berry_animation/animation_docs/Troubleshooting.md new file mode 100644 index 000000000..21efae3c3 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/Troubleshooting.md @@ -0,0 +1,1238 @@ +# Troubleshooting Guide + +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` or `import animation_dsl` fails with "module not found" + +**Solutions:** +1. **Check Module Import:** + ```berry + import animation # Core framework + import animation_dsl # DSL compiler + ``` + +2. **Set Module Path:** + ```bash + berry -m lib/libesp32/berry_animation + ``` + +3. **Verify File Structure:** + ``` + lib/libesp32/berry_animation/ + ├── animation.be # Main module file + ├── dsl/ # DSL components + ├── core/ # Core classes + ├── animations/ # Animation effects + └── ... + ``` + +### Missing Dependencies + +**Problem:** Errors about missing `tasmota` or `Leds` classes + +**Solutions:** +1. **For Tasmota Environment:** + - Ensure you're running on actual Tasmota firmware + - Check that Berry support is enabled + +2. **For Development Environment:** + ```berry + # Mock Tasmota for testing + if !global.contains("tasmota") + global.tasmota = { + "millis": def() return 1000 end, + "scale_uint": def(val, from_min, from_max, to_min, to_max) + return int((val - from_min) * (to_max - to_min) / (from_max - from_min) + to_min) + end + } + end + ``` + +## Animation Issues + +### Animations Not Starting + +**Problem:** DSL animations compile but LEDs don't change + +**Diagnostic Steps:** +```berry +import animation +import animation_dsl + +# Test basic DSL execution +var dsl_code = "color red = 0xFF0000\n" + + "animation red_anim = solid(color=red)\n" + + "run red_anim" + +try + animation_dsl.execute(dsl_code) + print("DSL executed successfully") +except .. as e, msg + print("DSL Error:", msg) +end +``` + +**Timing Behavior Note:** +The framework has updated timing behavior where: +- The `start()` method only resets the time origin if the animation/value provider was already started previously +- The first actual rendering tick occurs in `update()`, `render()`, or `produce_value()` methods +- This ensures proper timing initialization and prevents premature time reference setting + +**Common Solutions:** + +1. **Missing Strip Declaration:** + ```berry + # Add explicit strip length if needed + strip length 30 + + color red = 0xFF0000 + animation red_anim = solid(color=red) + run red_anim + ``` + +2. **Animation Not Executed:** + ```berry + # 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 Auto-Detection Issues:** + ```berry + # 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 + +**Problem:** Colors appear different than expected + +**Common Issues:** + +1. **Missing Alpha Channel:** + ```berry + # Note: 0xFF0000 is valid RGB format (alpha defaults to 0xFF) + color red = 0xFF0000 # RGB format (alpha=255 assumed) + + # 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 + # ARGB format: 0xAARRGGBB + 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 + # Use opacity parameter or property assignment + animation red_anim = solid(color=red, opacity=255) # Full brightness + + # 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 + +**Problem:** Animation timing doesn't match expectations + +**Solutions:** + +1. **Check Time Units:** + ```berry + # 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 + # Too fast - increase period + animation slow_pulse = pulsating_animation(color=red, period=5s) # 5 seconds + + # Too slow - decrease period + animation fast_pulse = pulsating_animation(color=red, period=500ms) # 0.5 seconds + ``` + +3. **Performance Limitations:** + ```berry + # 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 + +### DSL Compilation Errors + +**Problem:** DSL code fails to compile + +**Diagnostic Approach:** +```berry +try + var berry_code = animation_dsl.compile(dsl_source) + print("Compilation successful") +except "dsl_compilation_error" as e, msg + print("DSL Error:", msg) +end +``` + +**Common DSL Errors:** + +1. **Undefined Colors:** + ```berry + # Wrong - color not defined + animation red_anim = solid(color=red) + + # Correct - define color first + color red = 0xFF0000 + animation red_anim = solid(color=red) + ``` + +2. **Invalid Color Format:** + ```berry + # Wrong - # prefix not supported (conflicts with comments) + color red = #FF0000 + + # Correct - use 0x prefix + color red = 0xFF0000 + ``` + +3. **Missing Time Units:** + ```berry + # Wrong - no time unit + animation pulse_anim = pulsating_animation(color=red, period=2000) + + # Correct - with time unit + animation pulse_anim = pulsating_animation(color=red, period=2s) + ``` + +4. **Reserved Name Conflicts:** + ```berry + # Wrong - 'red' is a predefined color + color red = 0x800000 + + # Correct - use different name + color dark_red = 0x800000 + ``` + +5. **Invalid Parameter Names:** + ```berry + # 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. **Variable Duration Support:** + ```berry + # Now supported - variables in play/wait durations + set eye_duration = 5s + + sequence cylon_eye { + play red_eye for eye_duration # ✓ Variables now work + wait eye_duration # ✓ Variables work in wait too + } + + # Also supported - value providers for dynamic duration + set dynamic_time = triangle(min_value=1000, max_value=3000, period=10s) + + sequence demo { + play animation for dynamic_time # ✓ Dynamic duration + } + ``` + +7. **Template Definition Errors:** + ```berry + # Wrong - missing braces + template pulse_effect + param color type color + param speed + # Error: Expected '{' after template name + + # Wrong - invalid parameter syntax + template pulse_effect { + param color as color # Error: Use 'type' instead of 'as' + param speed + } + + # Wrong - missing template body + template pulse_effect { + param color type color + } + # Error: Template body cannot be empty + + # Correct - proper template syntax + template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation( + color=color + period=speed + ) + + run pulse + } + ``` + +8. **Template Call Errors:** + ```berry + # Wrong - template not defined + pulse_effect(red, 2s) + # Error: "Undefined reference: 'pulse_effect'" + + # Wrong - incorrect parameter count + template pulse_effect { + param color type color + param speed + # ... template body ... + } + + pulse_effect(red) # Error: Expected 2 parameters, got 1 + + # Correct - define template first, call with correct parameters + template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse + } + + pulse_effect(red, 2s) # ✓ Correct usage + ``` + +6. **Parameter Constraint Violations:** + ```berry + # 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) + ``` + +7. **Repeat Syntax Errors:** + ```berry + # Wrong - old colon syntax no longer supported + sequence bad_demo { + repeat 3 times: # Error: Expected '{' after 'times' + play anim for 1s + } + + # Wrong - missing braces + sequence bad_demo2 { + repeat 3 times + play anim for 1s # Error: Expected '{' after 'times' + } + + # Correct - use braces for repeat blocks + sequence good_demo { + repeat 3 times { + play anim for 1s + } + } + + # Also correct - alternative syntax + sequence good_demo_alt repeat 3 times { + play anim for 1s + } + + # Correct - forever syntax + sequence infinite_demo { + repeat forever { + play anim for 1s + wait 500ms + } + } + ``` + +### Template Issues + +### Template Definition Problems + +**Problem:** Template definitions fail to compile + +**Common Template Errors:** + +1. **Missing Template Body:** + ```berry + # Wrong - empty template + template empty_template { + param color type color + } + # Error: "Template body cannot be empty" + + # Correct - template must have content + template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse + } + ``` + +2. **Invalid Parameter Syntax:** + ```berry + # Wrong - old 'as' syntax + template pulse_effect { + param color as color + } + # Error: Expected 'type' keyword, got 'as' + + # Correct - use 'type' keyword + template pulse_effect { + param color type color + param speed # Type annotation is optional + } + ``` + +3. **Template Name Conflicts:** + ```berry + # Wrong - template name conflicts with built-in function + template solid { # 'solid' is a built-in animation function + param color type color + # ... + } + # Error: "Template name 'solid' conflicts with built-in function" + + # Correct - use unique template names + template solid_effect { + param color type color + # ... + } + ``` + +### Template Usage Problems + +**Problem:** Template calls fail or behave unexpectedly + +**Common Issues:** + +1. **Undefined Template:** + ```berry + # Wrong - calling undefined template + my_effect(red, 2s) + # Error: "Undefined reference: 'my_effect'" + + # Correct - define template first + template my_effect { + param color type color + param speed + # ... template body ... + } + + my_effect(red, 2s) # Now works + ``` + +2. **Parameter Count Mismatch:** + ```berry + template pulse_effect { + param color type color + param speed + param brightness + } + + # Wrong - missing parameters + pulse_effect(red, 2s) # Error: Expected 3 parameters, got 2 + + # Correct - provide all parameters + pulse_effect(red, 2s, 200) + ``` + +3. **Parameter Type Issues:** + ```berry + template pulse_effect { + param color type color + param speed + } + + # Wrong - invalid color parameter + pulse_effect("not_a_color", 2s) + # Runtime error: Invalid color value + + # Correct - use valid color + pulse_effect(red, 2s) # Named color + pulse_effect(0xFF0000, 2s) # Hex color + ``` + +### Template vs User Function Confusion + +**Problem:** Mixing template and user function concepts + +**Key Differences:** + +```berry +# Template (DSL-native) - Recommended for most cases +template pulse_effect { + param color type color + param speed + + animation pulse = pulsating_animation(color=color, period=speed) + run pulse +} + +# User Function (Berry-native) - For complex logic +def create_pulse_effect(engine, color, speed) + var pulse = animation.pulsating_animation(engine) + pulse.color = color + pulse.period = speed + return pulse +end +animation.register_user_function("pulse_effect", create_pulse_effect) +``` + +**When to Use Each:** +- **Templates**: Simple to moderate effects, DSL syntax, type safety +- **User Functions**: Complex logic, Berry features, return values + +## DSL Runtime Errors + +**Problem:** DSL compiles but fails at runtime + +**Common Issues:** + +1. **Strip Not Initialized:** + ```berry + # Add strip declaration if needed + strip length 30 + + color red = 0xFF0000 + animation red_anim = solid(color=red) + run red_anim + ``` + +2. **Repeat Performance Issues:** + ```berry + # Efficient - runtime repeats don't expand at compile time + sequence efficient { + repeat 1000 times { # No memory overhead for large counts + play anim for 100ms + wait 50ms + } + } + + # Nested repeats work efficiently + sequence nested { + repeat 100 times { + repeat 50 times { # Total: 5000 iterations, but efficient + play quick_flash for 10ms + } + wait 100ms + } + } + ``` + +3. **Sequence Issues:** + ```berry + # Make sure animations are defined before sequences + 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 + ``` + +4. **Undefined References:** + ```berry + # 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 + +### CPU Metrics and Profiling + +**Feature:** Built-in CPU metrics tracking to monitor animation performance + +The AnimationEngine automatically tracks CPU usage and provides detailed statistics every 5 seconds. This helps identify performance bottlenecks and optimize animations for ESP32 embedded systems. + +**Automatic Metrics:** + +When the engine is running, it automatically logs performance statistics: + +``` +AnimEngine: ticks=1000/1000 missed=0 total=0.50ms(0-2) anim=0.30ms(0-1) hw=0.20ms(0-1) cpu=10.0% + Phase1(checks): mean=0.05ms(0-0) + Phase2(events): mean=0.05ms(0-0) + Phase3(anim): mean=0.20ms(0-1) +``` + +**Metrics Explained:** +- **ticks**: Actual ticks executed vs expected (at 5ms intervals) +- **missed**: Hint of missed ticks (negative means extra ticks, positive means missed) +- **total**: Mean total tick time with (min-max) range in milliseconds +- **anim**: Mean animation calculation time with (min-max) range - everything before hardware output +- **hw**: Mean hardware output time with (min-max) range - just the LED strip update +- **cpu**: Overall CPU usage percentage over the 5-second period + +**Phase Metrics (Optional):** +When intermediate measurement points are available, the engine also reports phase-based timing: +- **Phase1(checks)**: Initial checks (strip length, throttling, can_show) +- **Phase2(events)**: Event processing time +- **Phase3(anim)**: Animation update and render time (before hardware output) + +**Timestamp-Based Profiling:** + +The engine uses a timestamp-based profiling system that stores only timestamps (not durations) in instance variables: + +- `ts_start` - Tick start timestamp +- `ts_1` - After initial checks (optional) +- `ts_2` - After event processing (optional) +- `ts_3` - After animation update/render (optional) +- `ts_hw` - After hardware output +- `ts_end` - Tick end timestamp + +Durations are computed from these timestamps in `_record_tick_metrics()` with nil checks to ensure values are valid. + +**Accessing Profiling Data:** + +```berry +import animation + +var strip = Leds(30) +var engine = animation.create_engine(strip) + +# Add an animation +var anim = animation.solid(engine) +anim.color = 0xFFFF0000 +engine.add(anim) +engine.run() + +# Run for a while to collect metrics +# After 5 seconds, metrics are automatically logged + +# Access current metrics programmatically +print("Tick count:", engine.tick_count) +print("Total time sum:", engine.tick_time_sum) +print("Animation time sum:", engine.anim_time_sum) +print("Hardware time sum:", engine.hw_time_sum) + +# Access phase metrics if available +if engine.phase1_time_sum > 0 + print("Phase 1 time sum:", engine.phase1_time_sum) +end +``` + +**Profiling Benefits:** + +1. **Memory Efficient:** + - Only stores timestamps (6 instance variables) + - No duration storage or arrays + - Streaming statistics with no memory overhead + +2. **Automatic Tracking:** + - No manual instrumentation needed + - Runs continuously in background + - Reports every 5 seconds + +3. **Detailed Breakdown:** + - Separates animation calculation from hardware output + - Optional phase-based timing for deeper analysis + - Min/max/mean statistics for all metrics + +**Interpreting Performance Metrics:** + +1. **High Animation Time:** + - Too many simultaneous animations + - Complex value provider calculations + - Inefficient custom effects + + **Solution:** Simplify animations or use sequences + +2. **High Hardware Time:** + - Large LED strip (many pixels) + - Slow SPI/I2C communication + - Hardware limitations + + **Solution:** Reduce update frequency or strip length + +3. **Missed Ticks:** + - CPU overload (total time > 5ms per tick) + - Other Tasmota tasks interfering + + **Solution:** Optimize animations or reduce complexity + +4. **High CPU Percentage:** + - Animations consuming too much CPU + - May affect other Tasmota functions + + **Solution:** Increase animation periods or reduce effects + +**Example Performance Optimization:** + +```berry +import animation + +var strip = Leds(60) +var engine = animation.create_engine(strip) + +# Before optimization - complex animation +var complex_anim = animation.rainbow_animation(engine) +complex_anim.period = 100 # Very fast, high CPU + +engine.add(complex_anim) +engine.run() + +# Check metrics after 5 seconds: +# AnimEngine: ticks=950/1000 missed=50 total=5.2ms(4-8) cpu=104.0% +# ^ Too slow! Missing ticks and over 100% CPU + +# After optimization - slower period +complex_anim.period = 2000 # 2 seconds instead of 100ms + +# Check metrics after 5 seconds: +# AnimEngine: ticks=1000/1000 missed=0 total=0.8ms(0-2) cpu=16.0% +# ^ Much better! All ticks processed, reasonable CPU usage +``` + +### Choppy Animations + +**Problem:** Animations appear jerky or stuttering + +**Solutions:** + +1. **Use Sequences Instead of Multiple Animations:** + ```berry + # 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 + # run animation1 + # run animation2 + # run animation3 + ``` + +2. **Increase Animation Periods:** + ```berry + # Smooth - longer periods + animation smooth_pulse = pulsating_animation(color=red, period=3s) + + # Choppy - very short periods + animation choppy_pulse = pulsating_animation(color=red, period=50ms) + ``` + +3. **Optimize Value Providers:** + ```berry + # Efficient - reuse providers + set breathing = smooth(min_value=50, max_value=255, period=2s) + + 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 + ``` + +4. **Monitor CPU Metrics:** + ```berry + # Check if CPU is overloaded + # Look for missed ticks or high CPU percentage in metrics + # AnimEngine: ticks=950/1000 missed=50 ... cpu=95.0% + # ^ This indicates performance issues + + # Use profiling to find bottlenecks + engine.profile_start("suspect_code") + # ... code that might be slow ... + engine.profile_end("suspect_code") + ``` + +### Memory Issues + +**Problem:** Out of memory errors or system crashes + +**Solutions:** + +1. **Clear Unused Animations:** + ```berry + # Clear before adding new animations + engine.clear() + engine.add(new_animation) + ``` + +2. **Limit Palette Size:** + ```berry + # Good - reasonable palette size + palette simple_fire = [ + (0, 0x000000), + (128, 0xFF0000), + (255, 0xFFFF00) + ] + + # Avoid - very large palettes + # palette huge_palette = [ + # (0, color1), (1, color2), ... (255, color256) + # ] + ``` + +3. **Use Sequences Instead of Simultaneous Animations:** + ```berry + # Memory efficient - sequential playback + sequence show { + play animation1 for 5s + play animation2 for 5s + play animation3 for 5s + } + + # Memory intensive - all at once + # run animation1 + # run animation2 + # run animation3 + ``` + +## Event System Issues + +### Events Not Triggering + +**Problem:** Event handlers don't execute + +**Diagnostic Steps:** +```berry +# Check if handler is registered +var handlers = animation.get_event_handlers("button_press") +print("Handler count:", size(handlers)) + +# Test event triggering +animation.trigger_event("test_event", {"debug": true}) +``` + +**Solutions:** + +1. **Verify Handler Registration:** + ```berry + def test_handler(event_data) + print("Event triggered:", event_data) + end + + var handler = animation.register_event_handler("test", test_handler, 0) + print("Handler registered:", handler != nil) + ``` + +2. **Check Event Names:** + ```berry + # Event names are case-sensitive + animation.register_event_handler("button_press", handler) # Correct + animation.trigger_event("button_press", {}) # Must match exactly + ``` + +3. **Verify Conditions:** + ```berry + def condition_func(event_data) + return event_data.contains("required_field") + end + + animation.register_event_handler("event", handler, 0, condition_func) + + # Event data must satisfy condition + animation.trigger_event("event", {"required_field": "value"}) + ``` + +## Hardware Issues + +### LEDs Not Responding + +**Problem:** Framework runs but LEDs don't light up + +**Hardware Checks:** + +1. **Power Supply:** + - Ensure adequate power for LED count + - Check voltage (5V for WS2812) + - Verify ground connections + +2. **Wiring:** + - Data line connected to correct GPIO + - Ground connected between controller and LEDs + - Check for loose connections + +3. **LED Strip:** + - Test with known working code + - Check for damaged LEDs + - Verify strip type (WS2812, SK6812, etc.) + +**Software Checks:** +```berry +# Test basic LED functionality +var strip = Leds(30) # 30 LEDs +strip.set_pixel_color(0, 0xFFFF0000) # Set first pixel red +strip.show() # Update LEDs + +# Test with animation framework +import animation +var engine = animation.create_engine(strip) +var red_anim = animation.solid(engine) +red_anim.color = 0xFFFF0000 +engine.add(red_anim) +engine.run() + +# If basic strip works but animation doesn't, check framework setup +``` + +### Wrong Colors on Hardware + +**Problem:** Colors look different on actual LEDs vs. expected + +**Solutions:** + +1. **Color Order:** + ```berry + # Some strips use different color orders + # Try different strip types in Tasmota configuration + # WS2812: RGB order + # SK6812: GRBW order + ``` + +2. **Gamma Correction:** + ```berry + # Enable gamma correction in Tasmota + # SetOption37 128 # Enable gamma correction + ``` + +3. **Power Supply Issues:** + - Voltage drop causes color shifts + - Use adequate power supply + - Add power injection for long strips + +## Debugging Techniques + +### DSL vs Berry API Debugging + +**For DSL Issues (Recommended):** +```berry +# Enable DSL debug output +import animation_dsl + +var dsl_code = "color red = 0xFF0000\nanimation test = solid(color=red)\nrun test" + +# Check compilation +try + var berry_code = animation_dsl.compile(dsl_code) + print("DSL compilation successful") + print("Generated Berry code:") + print(berry_code) +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(anim) +engine.run() +``` + +### Step-by-Step Testing + +```berry +# Test each component individually +print("1. Creating strip...") +var strip = Leds(30) +print("Strip created:", strip != nil) + +print("2. Creating engine...") +var engine = animation.create_engine(strip) +print("Engine created:", engine != nil) + +print("3. Creating animation...") +var anim = animation.solid(engine) +anim.color = 0xFFFF0000 +print("Animation created:", anim != nil) + +print("4. Adding animation...") +engine.add(anim) +print("Animation count:", engine.size()) + +print("5. Starting engine...") +engine.run() +print("Engine active:", engine.is_active()) +``` + +### Monitor Performance + +```berry +# Check timing +var start_time = tasmota.millis() +# ... run animation code ... +var end_time = tasmota.millis() +print("Execution time:", end_time - start_time, "ms") + +# Monitor memory (if available) +import gc +print("Memory before:", gc.allocated()) +# ... create animations ... +print("Memory after:", gc.allocated()) +``` + +## Getting Help + +### Information to Provide + +When asking for help, include: + +1. **Hardware Setup:** + - LED strip type and count + - GPIO pin used + - Power supply specifications + +2. **Software Environment:** + - Tasmota version + - Berry version + - Framework version + +3. **Code:** + - Complete minimal example that reproduces the issue + - Error messages (exact text) + - Expected vs. actual behavior + +4. **Debugging Output:** + - Debug mode output + - Generated Berry code (for DSL issues) + - Console output + +### Example Bug Report + +``` +**Problem:** DSL animation compiles but LEDs don't change + +**Hardware:** +- 30x WS2812 LEDs on GPIO 1 +- ESP32 with 5V/2A power supply + +**Code:** +```berry +color red = 0xFF0000 +animation red_anim = solid(color=red) +run red_anim +``` + +**Error Output:** +``` +DSL compilation successful +Engine created: true +Animation count: 1 +Engine active: true +``` + +**Expected:** LEDs turn red +**Actual:** LEDs remain off + +**Additional Info:** +- Basic `strip.set_pixel_color(0, 0xFFFF0000); strip.show()` works +- Tasmota 13.2.0, Berry enabled +``` + +This format helps identify issues quickly and provide targeted solutions. + +## Prevention Tips + +### Code Quality + +1. **Use Try-Catch Blocks:** + ```berry + try + runtime.load_dsl(dsl_code) + except .. as e, msg + print("Error:", msg) + end + ``` + +2. **Validate Inputs:** + ```berry + if type(color) == "int" && color >= 0 + var anim = animation.solid(color) + else + print("Invalid color:", color) + end + ``` + +3. **Test Incrementally:** + - Start with simple solid colors + - Add one effect at a time + - Test each change before proceeding + +### Performance Best Practices + +1. **Limit Complexity:** + - 1-3 simultaneous animations + - Reasonable animation periods (>1 second) + - Moderate palette sizes + +2. **Resource Management:** + - Clear unused animations + - Reuse value providers + - Use sequences for complex shows + +3. **Hardware Considerations:** + - Adequate power supply + - Proper wiring and connections + - Appropriate LED strip for application + +## Quick Reference: Common DSL Patterns + +### Basic Animation +```berry +color red = 0xFF0000 +animation red_solid = solid(color=red) +run red_solid +``` + +### Templates +```berry +# Define reusable template +template pulse_effect { + param base_color type color # Use descriptive names + param speed type time # Add type annotations for clarity + + animation pulse = pulsating_animation(color=base_color, period=speed) + run pulse +} + +# Use template multiple times +pulse_effect(red, 2s) +pulse_effect(blue, 1s) +``` + +**Common Template Parameter Issues:** + +```berry +# ❌ AVOID: Parameter name conflicts +template bad_example { + param color type color # Error: conflicts with built-in color name + param animation type number # Error: conflicts with reserved keyword +} + +# ✅ CORRECT: Use descriptive, non-conflicting names +template good_example { + param base_color type color # Clear, non-conflicting name + param anim_speed type time # Descriptive parameter name +} + +# ⚠️ WARNING: Unused parameters generate warnings +template unused_param_example { + param used_color type color + param unused_value type number # Warning: never used in template body + + animation test = solid(color=used_color) + run test +} +``` + +### Animation with Parameters +```berry +color blue = 0x0000FF +animation blue_pulse = pulsating_animation(color=blue, period=2s, opacity=200) +run blue_pulse +``` + +### Using Value Providers +```berry +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 +```berry +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 +```berry +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. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/animation_docs/User_Functions.md b/lib/libesp32/berry_animation/animation_docs/User_Functions.md new file mode 100644 index 000000000..63e60e641 --- /dev/null +++ b/lib/libesp32/berry_animation/animation_docs/User_Functions.md @@ -0,0 +1,677 @@ +# User-Defined Functions + +Create custom animation functions in Berry and use them seamlessly in the Animation DSL. + +## Quick Start + +### 1. Create Your Function + +Write a Berry function that creates and returns an animation: + +```berry +# Define a custom breathing effect +def my_breathing(engine, color, speed) + var anim = animation.pulsating_animation(engine) + anim.color = color + anim.min_brightness = 50 + anim.max_brightness = 255 + anim.period = speed + return anim +end +``` + +### 2. Register It + +Make your function available in DSL: + +```berry +animation.register_user_function("breathing", my_breathing) +``` + +### 3. Use It in DSL + +First, import your user functions module, then call your function directly in computed parameters: + +```berry +# Import your user functions module +import user_functions + +# Use your custom function in computed parameters +animation calm = solid(color=blue) +calm.opacity = breathing_effect() + +animation energetic = solid(color=red) +energetic.opacity = breathing_effect() + +sequence demo { + play calm for 10s + play energetic for 5s +} + +run demo +``` + +## Importing User Functions + +### DSL Import Statement + +The DSL supports importing Berry modules using the `import` keyword. This is the recommended way to make user functions available in your animations: + +```berry +# Import user functions at the beginning of your DSL file +import user_functions + +# Now user functions are available directly +animation test = solid(color=blue) +test.opacity = my_function() +``` + +### Import Behavior + +- **Module Loading**: `import user_functions` transpiles to Berry `import "user_functions"` +- **Function Registration**: The imported module should register functions using `animation.register_user_function()` +- **Availability**: Once imported, functions are available throughout the DSL file +- **No Compile-Time Checking**: The DSL doesn't validate user function existence at compile time + +### Example User Functions Module + +Create a file called `user_functions.be`: + +```berry +import animation + +# Define your custom functions +def rand_demo(engine) + import math + return math.rand() % 256 # Random value 0-255 +end + +def breathing_effect(engine, base_value, amplitude) + import math + var time_factor = (engine.time_ms / 1000) % 4 # 4-second cycle + var breath = math.sin(time_factor * math.pi / 2) + return int(base_value + breath * amplitude) +end + +# Register functions for DSL use +animation.register_user_function("rand_demo", rand_demo) +animation.register_user_function("breathing", breathing_effect) + +print("User functions loaded!") +``` + +### Using Imported Functions in DSL + +```berry +import user_functions + +# Simple user function call +animation random_test = solid(color=red) +random_test.opacity = rand_demo() + +# User function with parameters +animation breathing_blue = solid(color=blue) +breathing_blue.opacity = breathing(128, 64) + +# User functions in mathematical expressions +animation complex = solid(color=green) +complex.opacity = max(50, min(255, rand_demo() + 100)) + +run random_test +``` + +### Multiple Module Imports + +You can import multiple modules in the same DSL file: + +```berry +import user_functions # Basic user functions +import fire_effects # Fire animation functions +import color_utilities # Color manipulation functions + +animation base = solid(color=random_color()) +base.opacity = breathing(200, 50) + +animation flames = solid(color=red) +flames.opacity = fire_intensity(180) +``` + +## Common Patterns + +### Simple Color Effects + +```berry +def solid_bright(engine, color, brightness_percent) + var anim = animation.solid_animation(engine) + anim.color = color + anim.brightness = int(brightness_percent * 255 / 100) + return anim +end + +animation.register_user_function("bright", solid_bright) +``` + +```berry +animation bright_red = solid(color=red) +bright_red.opacity = bright(80) + +animation dim_blue = solid(color=blue) +dim_blue.opacity = bright(30) +``` + +### Fire Effects + +```berry +def custom_fire(engine, intensity, speed) + var color_provider = animation.rich_palette(engine) + color_provider.colors = animation.PALETTE_FIRE + color_provider.period = speed + + var fire_anim = animation.filled(engine) + fire_anim.color_provider = color_provider + fire_anim.brightness = intensity + return fire_anim +end + +animation.register_user_function("fire", custom_fire) +``` + +```berry +animation campfire = solid(color=red) +campfire.opacity = fire(200, 2000) + +animation torch = solid(color=orange) +torch.opacity = fire(255, 500) +``` + +### Twinkling Effects + +```berry +def twinkles(engine, color, count, period) + var anim = animation.twinkle_animation(engine) + anim.color = color + anim.count = count + anim.period = period + return anim +end + +animation.register_user_function("twinkles", twinkles) +``` + +```berry +animation stars = solid(color=white) +stars.opacity = twinkles(12, 800ms) + +animation fairy_dust = solid(color=0xFFD700) +fairy_dust.opacity = twinkles(8, 600ms) +``` + +### Position-Based Effects + +```berry +def pulse_at(engine, color, position, width, speed) + var anim = animation.beacon_animation(engine) + anim.color = color + anim.position = position + anim.width = width + anim.period = speed + return anim +end + +animation.register_user_function("pulse_at", pulse_at) +``` + +```berry +animation left_pulse = solid(color=green) +left_pulse.position = pulse_at(5, 3, 2000) + +animation right_pulse = solid(color=blue) +right_pulse.position = pulse_at(25, 3, 2000) +``` + +## Advanced Examples + +### Multi-Layer Effects + +```berry +def rainbow_twinkle(engine, base_speed, twinkle_density) + # Create base rainbow animation + var rainbow_provider = animation.rich_palette(engine) + rainbow_provider.colors = animation.PALETTE_RAINBOW + rainbow_provider.period = base_speed + + var base_anim = animation.filled(engine) + base_anim.color_provider = rainbow_provider + base_anim.priority = 1 + + # Note: This is a simplified example + # Real multi-layer effects would require engine support + return base_anim +end + +animation.register_user_function("rainbow_sparkle", rainbow_sparkle) +``` + +### Dynamic Palettes + +Since DSL palettes only accept hex colors and predefined color names (not custom colors), use user functions for dynamic palettes with custom colors: + +```berry +def create_custom_palette(engine, base_color, variation_count, intensity) + # Create a palette with variations of the base color + var palette_bytes = bytes() + + # Extract RGB components from base color + var r = (base_color >> 16) & 0xFF + var g = (base_color >> 8) & 0xFF + var b = base_color & 0xFF + + # Create palette entries with color variations + for i : 0..(variation_count-1) + var position = int(i * 255 / (variation_count - 1)) + var factor = intensity * i / (variation_count - 1) / 255 + + var new_r = int(r * factor) + var new_g = int(g * factor) + var new_b = int(b * factor) + + # Add VRGB entry (Value, Red, Green, Blue) + palette_bytes.add(position, 1) # Position + palette_bytes.add(new_r, 1) # Red + palette_bytes.add(new_g, 1) # Green + palette_bytes.add(new_b, 1) # Blue + end + + return palette_bytes +end + +animation.register_user_function("custom_palette", create_custom_palette) +``` + +```berry +# Use dynamic colors in DSL +animation gradient_effect = rich_palette( + colors=custom_palette(0xFF6B35, 5, 255) + period=4s +) + +run gradient_effect +``` + +### Preset Configurations + +```berry +def police_lights(engine, flash_speed) + var anim = animation.pulsating_animation(engine) + anim.color = 0xFFFF0000 # Red + anim.min_brightness = 0 + anim.max_brightness = 255 + anim.period = flash_speed + return anim +end + +def warning_strobe(engine) + return police_lights(engine, 200) # Fast strobe +end + +def gentle_alert(engine) + return police_lights(engine, 1000) # Slow pulse +end + +animation.register_user_function("police", police_lights) +animation.register_user_function("strobe", warning_strobe) +animation.register_user_function("alert", gentle_alert) +``` + +```berry +animation emergency = solid(color=red) +emergency.opacity = strobe() + +animation notification = solid(color=yellow) +notification.opacity = alert() + +animation custom_police = solid(color=blue) +custom_police.opacity = police(500) +``` + +## Function Organization + +### Single File Approach + +```berry +# user_animations.be +import animation + +def breathing(engine, color, period) + # ... implementation +end + +def fire_effect(engine, intensity, speed) + # ... implementation +end + +def twinkle_effect(engine, color, count, period) + # ... implementation +end + +# Register all functions +animation.register_user_function("breathing", breathing) +animation.register_user_function("fire", fire_effect) +animation.register_user_function("twinkle", twinkle_effect) + +print("Custom animations loaded!") +``` + +### Modular Approach + +```berry +# animations/fire.be +def fire_effect(engine, intensity, speed) + # ... implementation +end + +def torch_effect(engine) + return fire_effect(engine, 255, 500) +end + +return { + 'fire': fire_effect, + 'torch': torch_effect +} +``` + +```berry +# main.be +import animation + +# Register functions +animation.register_user_function("fire", fire_effects['fire']) +animation.register_user_function("torch", fire_effects['torch']) +``` + +## Best Practices + +### Function Design + +1. **Use descriptive names**: `breathing_slow` not `bs` +2. **Logical parameter order**: color first, then timing, then modifiers +3. **Sensible defaults**: Make functions work with minimal parameters +4. **Return animations**: Always return a configured animation object + +### Parameter Handling + +```berry +def flexible_pulse(engine, color, period, min_brightness, max_brightness) + # Provide defaults for optional parameters + if min_brightness == nil min_brightness = 50 end + if max_brightness == nil max_brightness = 255 end + + var anim = animation.pulsating_animation(engine) + anim.color = color + anim.period = period + anim.min_brightness = min_brightness + anim.max_brightness = max_brightness + return anim +end +``` + +### Error Handling + +```berry +def safe_comet(engine, color, tail_length, speed) + # Validate parameters + if tail_length < 1 tail_length = 1 end + if tail_length > 20 tail_length = 20 end + if speed < 100 speed = 100 end + + var anim = animation.comet_animation(engine) + anim.color = color + anim.tail_length = tail_length + anim.speed = speed + return anim +end +``` + +### Documentation + +```berry +# Creates a pulsing animation with customizable brightness range +# Parameters: +# color: The color to pulse (hex or named color) +# period: How long one pulse cycle takes (in milliseconds) +# min_brightness: Minimum brightness (0-255, default: 50) +# max_brightness: Maximum brightness (0-255, default: 255) +# Returns: Configured pulse animation +def breathing_effect(engine, color, period, min_brightness, max_brightness) + # ... implementation +end +``` + +## User Functions in Computed Parameters + +User functions can be used in computed parameter expressions alongside mathematical functions, creating powerful dynamic animations: + +### Simple User Function in Computed Parameter + +```berry +# Simple user function call in property assignment +animation base = solid(color=blue, priority=10) +base.opacity = rand_demo() # User function as computed parameter +``` + +### User Functions with Mathematical Operations + +```berry +# Get strip length for calculations +set strip_len = strip_length() + +# Mix user functions with mathematical functions +animation dynamic_solid = solid( + color=purple + opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds + priority=15 +) +``` + +### User Functions in Complex Expressions + +```berry +# Use user function in arithmetic expressions +animation random_effect = solid( + color=cyan + opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value + priority=12 +) +``` + +### How It Works + +When you use user functions in computed parameters: + +1. **Automatic Detection**: The transpiler automatically detects user functions in expressions +2. **Single Closure**: The entire expression is wrapped in a single efficient closure +3. **Engine Access**: User functions receive `engine` in the closure context +4. **Mixed Operations**: User functions work seamlessly with mathematical functions and arithmetic + +**Generated Code Example:** +```berry +# DSL code +animation.opacity = max(100, breathing(red, 2000)) +``` + +**Transpiles to:** +```berry +animation.opacity = animation.create_closure_value(engine, + def (engine, param_name, time_ms) + return (animation._math.max(100, animation.get_user_function('breathing')(engine, 0xFFFF0000, 2000))) + end) +``` + +### Available User Functions + +The following user functions are available by default: + +| Function | Parameters | Description | +|----------|------------|-------------| +| `rand_demo()` | none | Returns a random value (0-255) for demonstration | + +### Best Practices for Computed Parameters + +1. **Keep expressions readable**: Break complex expressions across multiple lines +2. **Use meaningful variable names**: `set strip_len = strip_length()` not `set s = strip_length()` +3. **Combine wisely**: Mix user functions with math functions for rich effects +4. **Test incrementally**: Start simple and build up complex expressions + +## Loading and Using Functions + +### In Tasmota autoexec.be + +```berry +import animation + +# Load your custom functions +load("user_animations.be") + +# Now they're available in DSL with import +var dsl_code = + "import user_functions\n" + "\n" + "animation my_fire = solid(color=red)\n" + "my_fire.opacity = fire(200, 1500)\n" + "animation my_twinkles = solid(color=white)\n" + "my_twinkles.opacity = twinkle(8, 400ms)\n" + "\n" + "sequence show {\n" + " play my_fire for 10s\n" + " play my_twinkles for 5s\n" + "}\n" + "\n" + "run show" + +animation_dsl.execute(dsl_code) +``` + +### From Files + +```berry +# Save DSL with custom functions +var my_show = + "import user_functions\n" + "\n" + "animation campfire = solid(color=orange)\n" + "campfire.opacity = fire(180, 2000)\n" + "animation stars = solid(color=0xFFFFFF)\n" + "stars.opacity = twinkle(6, 600ms)\n" + "\n" + "sequence night_scene {\n" + " play campfire for 30s\n" + " play stars for 10s\n" + "}\n" + "\n" + "run night_scene" + +# Save to file +var f = open("night_scene.anim", "w") +f.write(my_show) +f.close() + +# Load and run +animation_dsl.load_file("night_scene.anim") +``` + +## Implementation Details + +### Function Signature Requirements + +User functions must follow this exact pattern: + +```berry +def function_name(engine, param1, param2, ...) + # engine is ALWAYS the first parameter + # followed by user-provided parameters + return animation_object +end +``` + +### How the DSL Transpiler Works + +When you write DSL like this: +```berry +animation my_anim = my_function(arg1, arg2) +``` + +The transpiler generates Berry code like this: +```berry +var my_anim_ = animation.get_user_function('my_function')(engine, arg1, arg2) +``` + +The `engine` parameter is automatically inserted as the first argument. + +### Registration API + +```berry +# Register a function +animation.register_user_function(name, function) + +# Check if a function is registered +if animation.is_user_function("my_function") + print("Function is registered") +end + +# Get a registered function +var func = animation.get_user_function("my_function") + +# List all registered functions +var functions = animation.list_user_functions() +for name : functions + print("Registered:", name) +end +``` + +### Engine Parameter + +The `engine` parameter provides: +- Access to the LED strip: `engine.get_strip_length()` +- Current time: `engine.time_ms` +- Animation management context + +Always use the provided engine when creating animations - don't create your own engine instances. + +### Return Value Requirements + +User functions must return an animation object that: +- Extends `animation.animation` or `animation.pattern` +- Is properly configured with the engine +- Has all required parameters set + +### Error Handling + +The framework handles errors gracefully: +- Invalid function names are caught at DSL compile time +- Runtime errors in user functions are reported with context +- Failed function calls don't crash the animation system + +## Troubleshooting + +### Function Not Found +``` +Error: Unknown function 'my_function' +``` +- Ensure the function is registered with `animation.register_user_function()` +- Check that registration happens before DSL compilation +- Verify the function name matches exactly (case-sensitive) + +### Wrong Number of Arguments +``` +Error: Function call failed +``` +- Check that your function signature matches the DSL call +- Remember that `engine` is automatically added as the first parameter +- Verify all required parameters are provided in the DSL + +### Animation Not Working +- Ensure your function returns a valid animation object +- Check that the animation is properly configured +- Verify that the engine parameter is used correctly + +User-defined functions provide a powerful way to extend the Animation DSL with custom effects while maintaining the clean, declarative syntax that makes the DSL easy to use. \ No newline at end of file diff --git a/lib/libesp32/berry_animation/berry_animation_docs/emulator_screenshot.png b/lib/libesp32/berry_animation/animation_docs/emulator_screenshot.png similarity index 100% rename from lib/libesp32/berry_animation/berry_animation_docs/emulator_screenshot.png rename to lib/libesp32/berry_animation/animation_docs/emulator_screenshot.png diff --git a/lib/libesp32/berry_animation/src/animations/crenel_position.be b/lib/libesp32/berry_animation/src/animations/crenel_position.be index ce3176c68..3c4b07253 100644 --- a/lib/libesp32/berry_animation/src/animations/crenel_position.be +++ b/lib/libesp32/berry_animation/src/animations/crenel_position.be @@ -27,7 +27,7 @@ class CrenelPositionAnimation : animation.animation # Parameter definitions with constraints static var PARAMS = animation.enc_params({ # 'color' for the comet head (32-bit ARGB value), inherited from animation class - "back_color": {"default": 0xFF000000}, # background color, TODO change to transparent + "back_color": {"default": 0x00000000}, # background color (transparent by default) "pos": {"default": 0}, # start of the pulse (in pixel) "pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse "low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3 @@ -52,7 +52,7 @@ class CrenelPositionAnimation : animation.animation var period = int(pulse_size + low_size) # Fill background if not transparent - if back_color != 0xFF000000 + if back_color != 0x00000000 frame.fill_pixels(frame.pixels, back_color) end diff --git a/lib/libesp32/berry_animation/src/solidify/solidified_animation.h b/lib/libesp32/berry_animation/src/solidify/solidified_animation.h index 6dd31c593..964b78cc8 100644 --- a/lib/libesp32/berry_animation/src/solidify/solidified_animation.h +++ b/lib/libesp32/berry_animation/src/solidify/solidified_animation.h @@ -17747,27 +17747,26 @@ be_local_closure(twinkle_gentle, /* name */ ); /*******************************************************************/ -// compact class 'CrenelPositionAnimation' ktab size: 19, total: 24 (saved 40 bytes) -static const bvalue be_ktab_class_CrenelPositionAnimation[19] = { +// compact class 'CrenelPositionAnimation' ktab size: 18, total: 23 (saved 40 bytes) +static const bvalue be_ktab_class_CrenelPositionAnimation[18] = { /* K0 */ be_nested_str_weak(back_color), /* K1 */ be_nested_str_weak(pos), /* K2 */ be_nested_str_weak(pulse_size), /* K3 */ be_nested_str_weak(low_size), /* K4 */ be_nested_str_weak(nb_pulse), /* K5 */ be_nested_str_weak(color), - /* K6 */ be_const_int(-16777216), + /* K6 */ be_const_int(0), /* K7 */ be_nested_str_weak(fill_pixels), /* K8 */ be_nested_str_weak(pixels), - /* K9 */ be_const_int(0), - /* K10 */ be_const_int(1), - /* K11 */ be_nested_str_weak(set_pixel_color), - /* K12 */ be_nested_str_weak(get_param), - /* K13 */ be_nested_str_weak(animation), - /* K14 */ be_nested_str_weak(is_value_provider), - /* K15 */ be_nested_str_weak(0x_X2508x), - /* K16 */ be_nested_str_weak(CrenelPositionAnimation_X28color_X3D_X25s_X2C_X20pos_X3D_X25s_X2C_X20pulse_size_X3D_X25s_X2C_X20low_size_X3D_X25s_X2C_X20nb_pulse_X3D_X25s_X2C_X20priority_X3D_X25s_X2C_X20running_X3D_X25s_X29), - /* K17 */ be_nested_str_weak(priority), - /* K18 */ be_nested_str_weak(is_running), + /* K9 */ be_const_int(1), + /* K10 */ be_nested_str_weak(set_pixel_color), + /* K11 */ be_nested_str_weak(get_param), + /* K12 */ be_nested_str_weak(animation), + /* K13 */ be_nested_str_weak(is_value_provider), + /* K14 */ be_nested_str_weak(0x_X2508x), + /* K15 */ be_nested_str_weak(CrenelPositionAnimation_X28color_X3D_X25s_X2C_X20pos_X3D_X25s_X2C_X20pulse_size_X3D_X25s_X2C_X20low_size_X3D_X25s_X2C_X20nb_pulse_X3D_X25s_X2C_X20priority_X3D_X25s_X2C_X20running_X3D_X25s_X29), + /* K16 */ be_nested_str_weak(priority), + /* K17 */ be_nested_str_weak(is_running), }; @@ -17805,36 +17804,36 @@ be_local_closure(class_CrenelPositionAnimation_render, /* name */ 0x88340308, // 000C GETMBR R13 R1 K8 0x5C380800, // 000D MOVE R14 R4 0x7C2C0600, // 000E CALL R11 3 - 0x182C1509, // 000F LE R11 R10 K9 + 0x182C1506, // 000F LE R11 R10 K6 0x782E0000, // 0010 JMPF R11 #0012 - 0x5828000A, // 0011 LDCONST R10 K10 - 0x1C2C1109, // 0012 EQ R11 R8 K9 + 0x58280009, // 0011 LDCONST R10 K9 + 0x1C2C1106, // 0012 EQ R11 R8 K6 0x782E0001, // 0013 JMPF R11 #0016 0x502C0200, // 0014 LDBOOL R11 1 0 0x80041600, // 0015 RET 1 R11 - 0x142C1109, // 0016 LT R11 R8 K9 + 0x142C1106, // 0016 LT R11 R8 K6 0x782E0006, // 0017 JMPF R11 #001F 0x002C0A06, // 0018 ADD R11 R5 R6 - 0x042C170A, // 0019 SUB R11 R11 K10 + 0x042C1709, // 0019 SUB R11 R11 K9 0x102C160A, // 001A MOD R11 R11 R10 0x042C1606, // 001B SUB R11 R11 R6 - 0x002C170A, // 001C ADD R11 R11 K10 + 0x002C1709, // 001C ADD R11 R11 K9 0x5C141600, // 001D MOVE R5 R11 0x70020007, // 001E JMP #0027 0x442C1400, // 001F NEG R11 R10 0x142C0A0B, // 0020 LT R11 R5 R11 0x782E0004, // 0021 JMPF R11 #0027 - 0x202C1109, // 0022 NE R11 R8 K9 + 0x202C1106, // 0022 NE R11 R8 K6 0x782E0002, // 0023 JMPF R11 #0027 0x00140A0A, // 0024 ADD R5 R5 R10 - 0x0420110A, // 0025 SUB R8 R8 K10 + 0x04201109, // 0025 SUB R8 R8 K9 0x7001FFF7, // 0026 JMP #001F 0x142C0A03, // 0027 LT R11 R5 R3 0x782E0014, // 0028 JMPF R11 #003E - 0x202C1109, // 0029 NE R11 R8 K9 + 0x202C1106, // 0029 NE R11 R8 K6 0x782E0012, // 002A JMPF R11 #003E - 0x582C0009, // 002B LDCONST R11 K9 - 0x14300B09, // 002C LT R12 R5 K9 + 0x582C0006, // 002B LDCONST R11 K6 + 0x14300B06, // 002C LT R12 R5 K6 0x78320001, // 002D JMPF R12 #0030 0x44300A00, // 002E NEG R12 R5 0x5C2C1800, // 002F MOVE R11 R12 @@ -17843,14 +17842,14 @@ be_local_closure(class_CrenelPositionAnimation_render, /* name */ 0x00300A0B, // 0032 ADD R12 R5 R11 0x14301803, // 0033 LT R12 R12 R3 0x78320005, // 0034 JMPF R12 #003B - 0x8C30030B, // 0035 GETMET R12 R1 K11 + 0x8C30030A, // 0035 GETMET R12 R1 K10 0x00380A0B, // 0036 ADD R14 R5 R11 0x5C3C1200, // 0037 MOVE R15 R9 0x7C300600, // 0038 CALL R12 3 - 0x002C170A, // 0039 ADD R11 R11 K10 + 0x002C1709, // 0039 ADD R11 R11 K9 0x7001FFF4, // 003A JMP #0030 0x00140A0A, // 003B ADD R5 R5 R10 - 0x0420110A, // 003C SUB R8 R8 K10 + 0x04201109, // 003C SUB R8 R8 K9 0x7001FFE8, // 003D JMP #0027 0x502C0200, // 003E LDBOOL R11 1 0 0x80041600, // 003F RET 1 R11 @@ -17878,11 +17877,11 @@ be_local_closure(class_CrenelPositionAnimation_tostring, /* name */ &be_const_str_solidified, ( &(const binstruction[30]) { /* code */ 0x4C040000, // 0000 LDNIL R1 - 0x8C08010C, // 0001 GETMET R2 R0 K12 + 0x8C08010B, // 0001 GETMET R2 R0 K11 0x58100005, // 0002 LDCONST R4 K5 0x7C080400, // 0003 CALL R2 2 - 0xB80E1A00, // 0004 GETNGBL R3 K13 - 0x8C0C070E, // 0005 GETMET R3 R3 K14 + 0xB80E1800, // 0004 GETNGBL R3 K12 + 0x8C0C070D, // 0005 GETMET R3 R3 K13 0x5C140400, // 0006 MOVE R5 R2 0x7C0C0400, // 0007 CALL R3 2 0x780E0004, // 0008 JMPF R3 #000E @@ -17892,19 +17891,19 @@ be_local_closure(class_CrenelPositionAnimation_tostring, /* name */ 0x5C040600, // 000C MOVE R1 R3 0x70020004, // 000D JMP #0013 0x600C0018, // 000E GETGBL R3 G24 - 0x5810000F, // 000F LDCONST R4 K15 + 0x5810000E, // 000F LDCONST R4 K14 0x88140105, // 0010 GETMBR R5 R0 K5 0x7C0C0400, // 0011 CALL R3 2 0x5C040600, // 0012 MOVE R1 R3 0x600C0018, // 0013 GETGBL R3 G24 - 0x58100010, // 0014 LDCONST R4 K16 + 0x5810000F, // 0014 LDCONST R4 K15 0x5C140200, // 0015 MOVE R5 R1 0x88180101, // 0016 GETMBR R6 R0 K1 0x881C0102, // 0017 GETMBR R7 R0 K2 0x88200103, // 0018 GETMBR R8 R0 K3 0x88240104, // 0019 GETMBR R9 R0 K4 - 0x88280111, // 001A GETMBR R10 R0 K17 - 0x882C0112, // 001B GETMBR R11 R0 K18 + 0x88280110, // 001A GETMBR R10 R0 K16 + 0x882C0111, // 001B GETMBR R11 R0 K17 0x7C0C1000, // 001C CALL R3 8 0x80040600, // 001D RET 1 R3 }) @@ -17929,7 +17928,7 @@ be_local_class(CrenelPositionAnimation, { be_const_key_weak(low_size, 4), be_const_bytes_instance(0500000003) }, { be_const_key_weak(pos, 1), be_const_bytes_instance(040000) }, { be_const_key_weak(pulse_size, -1), be_const_bytes_instance(0500000001) }, - { be_const_key_weak(back_color, -1), be_const_bytes_instance(0402000000FF) }, + { be_const_key_weak(back_color, -1), be_const_bytes_instance(040000) }, })) ) } )) }, { be_const_key_weak(render, 2), be_const_closure(class_CrenelPositionAnimation_render_closure) }, { be_const_key_weak(tostring, -1), be_const_closure(class_CrenelPositionAnimation_tostring_closure) }, diff --git a/lib/libesp32/berry_animation/src/tests/crenel_position_animation_test.be b/lib/libesp32/berry_animation/src/tests/crenel_position_animation_test.be index 955e9d6d4..712061115 100644 --- a/lib/libesp32/berry_animation/src/tests/crenel_position_animation_test.be +++ b/lib/libesp32/berry_animation/src/tests/crenel_position_animation_test.be @@ -36,7 +36,7 @@ def run_tests() # Set parameters via virtual member assignment crenel.color = 0xFFFF0000 - crenel.back_color = 0xFF000000 + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 4 crenel.pulse_size = 2 crenel.low_size = 3 @@ -95,7 +95,7 @@ def run_tests() crenel.pulse_size = 2 crenel.low_size = 3 crenel.nb_pulse = -1 # Infinite - crenel.back_color = 0xFF000000 # Transparent + crenel.back_color = 0x00000000 # Transparent (default) crenel.start() var rendered = crenel.render(frame, engine.time_ms, engine.strip_length) @@ -124,7 +124,7 @@ def run_tests() # Test 7: Limited number of pulses frame.clear() - crenel.back_color = 0xFF000000 # Transparent background + crenel.back_color = 0x00000000 # Transparent background (default) crenel.nb_pulse = 2 # Only 2 pulses crenel.render(frame, engine.time_ms, engine.strip_length) diff --git a/lib/libesp32/berry_animation/src/tests/crenel_position_color_test.be b/lib/libesp32/berry_animation/src/tests/crenel_position_color_test.be index 6093c209d..e0989e5db 100644 --- a/lib/libesp32/berry_animation/src/tests/crenel_position_color_test.be +++ b/lib/libesp32/berry_animation/src/tests/crenel_position_color_test.be @@ -22,7 +22,7 @@ def test_crenel_with_integer_color() # Set parameters via virtual member assignment crenel.color = red_color - crenel.back_color = 0xFF000000 # transparent + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 0 crenel.pulse_size = 3 crenel.low_size = 2 @@ -64,7 +64,7 @@ def test_crenel_with_color_provider() # Set parameters via virtual member assignment crenel.color = color_provider # ColorProvider - crenel.back_color = 0xFF000000 # transparent + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 1 crenel.pulse_size = 2 crenel.low_size = 3 @@ -106,7 +106,7 @@ def test_crenel_with_dynamic_color_provider() # Set parameters via virtual member assignment crenel.color = palette_provider # dynamic ColorProvider - crenel.back_color = 0xFF000000 # transparent + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 0 crenel.pulse_size = 4 crenel.low_size = 1 @@ -154,7 +154,7 @@ def test_crenel_with_generic_value_provider() # Set parameters via virtual member assignment crenel.color = static_provider # generic ValueProvider - crenel.back_color = 0xFF000000 # transparent + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 2 crenel.pulse_size = 3 crenel.low_size = 2 @@ -191,7 +191,7 @@ def test_crenel_set_color_methods() # Set initial parameters crenel.color = 0xFFFF0000 # red - crenel.back_color = 0xFF000000 # transparent + crenel.back_color = 0x00000000 # transparent (default) crenel.pos = 0 crenel.pulse_size = 2 crenel.low_size = 1 @@ -233,7 +233,7 @@ def test_crenel_tostring() # Test with integer color var crenel_int = animation.crenel_animation(engine) crenel_int.color = 0xFFFF0000 - crenel_int.back_color = 0xFF000000 + crenel_int.back_color = 0x00000000 # transparent (default) crenel_int.pos = 0 crenel_int.pulse_size = 2 crenel_int.low_size = 1 @@ -254,7 +254,7 @@ def test_crenel_tostring() var crenel_provider = animation.crenel_animation(engine) crenel_provider.color = color_provider - crenel_provider.back_color = 0xFF000000 + crenel_provider.back_color = 0x00000000 # transparent (default) crenel_provider.pos = 0 crenel_provider.pulse_size = 2 crenel_provider.low_size = 1