From 677eaba2c19d119afa637d807f177c68981ff280 Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:43:55 +0100 Subject: [PATCH] Force remove all docs --- .../docs/ANIMATION_CLASS_HIERARCHY.md | 1207 ------------ .../docs/ANIMATION_DEVELOPMENT.md | 695 ------- .../berry_animation/docs/DSL_REFERENCE.md | 1695 ----------------- .../berry_animation/docs/DSL_TRANSPILATION.md | 798 -------- lib/libesp32/berry_animation/docs/EXAMPLES.md | 479 ----- .../docs/OSCILLATION_PATTERNS.md | 263 --- .../berry_animation/docs/QUICK_START.md | 276 --- .../docs/TRANSPILER_ARCHITECTURE.md | 858 --------- .../berry_animation/docs/TROUBLESHOOTING.md | 1238 ------------ .../berry_animation/docs/USER_FUNCTIONS.md | 677 ------- 10 files changed, 8186 deletions(-) delete mode 100644 lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md delete mode 100644 lib/libesp32/berry_animation/docs/ANIMATION_DEVELOPMENT.md delete mode 100644 lib/libesp32/berry_animation/docs/DSL_REFERENCE.md delete mode 100644 lib/libesp32/berry_animation/docs/DSL_TRANSPILATION.md delete mode 100644 lib/libesp32/berry_animation/docs/EXAMPLES.md delete mode 100644 lib/libesp32/berry_animation/docs/OSCILLATION_PATTERNS.md delete mode 100644 lib/libesp32/berry_animation/docs/QUICK_START.md delete mode 100644 lib/libesp32/berry_animation/docs/TRANSPILER_ARCHITECTURE.md delete mode 100644 lib/libesp32/berry_animation/docs/TROUBLESHOOTING.md delete mode 100644 lib/libesp32/berry_animation/docs/USER_FUNCTIONS.md diff --git a/lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md b/lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md deleted file mode 100644 index c4813456c..000000000 --- a/lib/libesp32/berry_animation/docs/ANIMATION_CLASS_HIERARCHY.md +++ /dev/null @@ -1,1207 +0,0 @@ -# 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(palette=colors, cycle_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 | -|-----------|------|---------|-------------|-------------| -| `palette` | bytes | default palette | - | Palette bytes in AARRGGBB format | -| `cycle_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 (`cycle_period > 0`) or Manual-only (`cycle_period = 0`) - -#### Usage Examples - -```berry -# RGB cycle with brutal switching -color rgb_cycle = color_cycle( - palette=bytes("FF0000FF" "FF00FF00" "FFFF0000"), - cycle_period=4s -) - -# Custom warm colors -color warm_cycle = color_cycle( - palette=bytes("FF4500FF" "FF8C00FF" "FFFF00"), - cycle_period=3s -) - -# Mixed colors in AARRGGBB format -color mixed_cycle = color_cycle( - palette=bytes("FFFF0000" "FF00FF00" "FF0000FF"), - cycle_period=2s -) -``` - -### RichPaletteColorProvider - -Generates colors from predefined palettes with smooth transitions and professional color schemes. Inherits from `ColorProvider`. - -| Parameter | Type | Default | Constraints | Description | -|-----------|------|---------|-------------|-------------| -| `palette` | bytes | rainbow palette | - | Palette bytes or predefined palette constant | -| `cycle_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 | -| `PALETTE_SUNSET_TICKS` | Sunset colors with equal timing | Orange Red → Dark Orange → Gold → Hot Pink → Purple → Midnight Blue | -| `PALETTE_OCEAN` | Blue and green ocean tones | Navy → Blue → Cyan → Spring Green → Green | -| `PALETTE_FOREST` | Various green forest tones | Dark Green → Forest Green → Lime Green → Mint Green → Light Green | - -#### Usage Examples - -```berry -# Rainbow palette with smooth ease-in/ease-out transitions -color rainbow_colors = rich_palette( - palette=PALETTE_RAINBOW, - cycle_period=5s, - transition_type=SINE, - brightness=255 -) - -# Fire effect with linear (constant speed) transitions -color fire_colors = rich_palette( - palette=PALETTE_FIRE, - cycle_period=3s, - transition_type=LINEAR, - brightness=200 -) - -# Ocean waves with default linear transitions -color ocean_colors = rich_palette( - palette=PALETTE_OCEAN, - cycle_period=8s, - brightness=180 -) -``` - -### 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(palette=bytes("FF0000FF" "FF00FF00" "FFFF0000"), cycle_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(palette=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 | -|-----------|------|---------|-------------|-------------| -| `palette` | bytes | rainbow palette | - | Palette bytes or predefined palette | -| `cycle_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.palette` instead of `anim.color.palette`) -- 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/docs/ANIMATION_DEVELOPMENT.md b/lib/libesp32/berry_animation/docs/ANIMATION_DEVELOPMENT.md deleted file mode 100644 index fbae87a28..000000000 --- a/lib/libesp32/berry_animation/docs/ANIMATION_DEVELOPMENT.md +++ /dev/null @@ -1,695 +0,0 @@ -# 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 == "palette" || 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/docs/DSL_REFERENCE.md b/lib/libesp32/berry_animation/docs/DSL_REFERENCE.md deleted file mode 100644 index 77c422013..000000000 --- a/lib/libesp32/berry_animation/docs/DSL_REFERENCE.md +++ /dev/null @@ -1,1695 +0,0 @@ -# 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( - palette=bytes("FFFF0000" "FF00FF00" "FF0000FF") - cycle_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( - palette=custom_palette(0xFF0000, 200) - cycle_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 - palette.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(palette=colors, cycle_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(palette=colors, cycle_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/docs/DSL_TRANSPILATION.md b/lib/libesp32/berry_animation/docs/DSL_TRANSPILATION.md deleted file mode 100644 index df710b3a9..000000000 --- a/lib/libesp32/berry_animation/docs/DSL_TRANSPILATION.md +++ /dev/null @@ -1,798 +0,0 @@ -# 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(palette=colors, cycle_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_.palette = animation.create_closure_value(engine, def (engine) return self.colors end) - col_.cycle_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/docs/EXAMPLES.md b/lib/libesp32/berry_animation/docs/EXAMPLES.md deleted file mode 100644 index 1030ad2a9..000000000 --- a/lib/libesp32/berry_animation/docs/EXAMPLES.md +++ /dev/null @@ -1,479 +0,0 @@ -# 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(palette=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(palette=eye_palette, cycle_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(palette=[red, yellow, green, blue], cycle_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 - palette rainbow = [ - (0, red), (42, orange), (85, yellow) - (128, green), (170, blue), (213, purple), (255, red) - ] - - # Create cycling rainbow color - color rainbow_cycle = color_cycle( - palette=rainbow - cycle_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/docs/OSCILLATION_PATTERNS.md b/lib/libesp32/berry_animation/docs/OSCILLATION_PATTERNS.md deleted file mode 100644 index 3b4a64e08..000000000 --- a/lib/libesp32/berry_animation/docs/OSCILLATION_PATTERNS.md +++ /dev/null @@ -1,263 +0,0 @@ -# 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/docs/QUICK_START.md b/lib/libesp32/berry_animation/docs/QUICK_START.md deleted file mode 100644 index 3bfccc176..000000000 --- a/lib/libesp32/berry_animation/docs/QUICK_START.md +++ /dev/null @@ -1,276 +0,0 @@ -# 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( - palette=PALETTE_RAINBOW - cycle_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( - palette=sunset - cycle_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( - palette=PALETTE_FIRE - cycle_period=2s - transition_type=1 -) - -run fire -``` - -### Ocean Waves -```berry -animation ocean = rich_palette( - palette=PALETTE_OCEAN - cycle_period=6s - transition_type=1 -) - -run ocean -``` - -## Tips for Success - -1. **Start Simple** - Begin with solid colors and basic effects -2. **Use Predefined Palettes** - Try PALETTE_RAINBOW, PALETTE_FIRE, PALETTE_OCEAN -3. **Test Incrementally** - Add one animation at a time -4. **Use Named Colors** - red, blue, green, white, etc. -5. **Start with Longer Periods** - 3-5 seconds, then adjust as needed - -## 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(palette=colors, cycle_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/docs/TRANSPILER_ARCHITECTURE.md b/lib/libesp32/berry_animation/docs/TRANSPILER_ARCHITECTURE.md deleted file mode 100644 index 5c4797f85..000000000 --- a/lib/libesp32/berry_animation/docs/TRANSPILER_ARCHITECTURE.md +++ /dev/null @@ -1,858 +0,0 @@ -# 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(palette=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/docs/TROUBLESHOOTING.md b/lib/libesp32/berry_animation/docs/TROUBLESHOOTING.md deleted file mode 100644 index f0b93cda7..000000000 --- a/lib/libesp32/berry_animation/docs/TROUBLESHOOTING.md +++ /dev/null @@ -1,1238 +0,0 @@ -# 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/docs/USER_FUNCTIONS.md b/lib/libesp32/berry_animation/docs/USER_FUNCTIONS.md deleted file mode 100644 index 5a0e9ecea..000000000 --- a/lib/libesp32/berry_animation/docs/USER_FUNCTIONS.md +++ /dev/null @@ -1,677 +0,0 @@ -# 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.palette = animation.PALETTE_FIRE - color_provider.cycle_period = speed - - var fire_anim = animation.filled(engine) - fire_anim.color_provider = color_provider - fire_anim.brightness = intensity - return fire_anim -end - -animation.register_user_function("fire", custom_fire) -``` - -```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.palette = animation.PALETTE_RAINBOW - rainbow_provider.cycle_period = base_speed - - var base_anim = animation.filled(engine) - base_anim.color_provider = rainbow_provider - base_anim.priority = 1 - - # Note: This is a simplified example - # Real multi-layer effects would require engine support - return base_anim -end - -animation.register_user_function("rainbow_sparkle", rainbow_sparkle) -``` - -### 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 palette in DSL -animation gradient_effect = rich_palette( - palette=custom_palette(0xFF6B35, 5, 255) - cycle_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