mirror of
https://github.com/arendst/Tasmota.git
synced 2026-07-27 20:05:46 +00:00
Berry animation remove value_provider class (#24320)
This commit is contained in:
@@ -15,6 +15,7 @@ This document provides a comprehensive reference for all classes in the Berry An
|
||||
|
||||
```
|
||||
parameterized_object (base class with parameter management and playable interface)
|
||||
│
|
||||
├── Animation (unified base class for all visual elements)
|
||||
│ ├── engine_proxy (combines rendering and orchestration)
|
||||
│ │ └── (user-defined template animations)
|
||||
@@ -30,19 +31,26 @@ parameterized_object (base class with parameter management and playable interfac
|
||||
│ ├── twinkle (twinkling stars effect)
|
||||
│ ├── wave (wave motion effects)
|
||||
│ └── rich_palette (smooth palette transitions)
|
||||
│
|
||||
├── sequence_manager (orchestrates animation sequences)
|
||||
└── value_provider (dynamic value generation)
|
||||
│
|
||||
└── Value Providers (VALUE_PROVIDER = true)
|
||||
│
|
||||
├── static_value (wraps static values)
|
||||
├── strip_length (provides LED strip length)
|
||||
├── iteration_number (provides sequence iteration number)
|
||||
├── oscillator_value (oscillating values with waveforms)
|
||||
│ └── breathe_color (breathing color effect)
|
||||
├── closure_value (computed values, internal use only)
|
||||
└── color_provider (solid color, base for color providers)
|
||||
│
|
||||
└── Color Providers
|
||||
├── color_provider (solid color, base for color providers)
|
||||
├── breathe_color (breathing color effect with internal oscillator)
|
||||
├── color_cycle (cycles through palette)
|
||||
└── rich_palette_color (smooth palette transitions)
|
||||
```
|
||||
|
||||
**Note on Value Providers**: Value providers inherit directly from `parameterized_object` and are identified by the static class variable `VALUE_PROVIDER = true`. Use `animation.is_value_provider(obj)` to check if an object is a value provider. This flat hierarchy reduces class overhead while maintaining clear semantic distinction.
|
||||
|
||||
## Base Classes
|
||||
|
||||
### parameterized_object
|
||||
@@ -198,25 +206,41 @@ run my_shutter
|
||||
|
||||
## Value Providers
|
||||
|
||||
Value providers generate dynamic values over time for use as animation parameters.
|
||||
Value providers generate dynamic values over time for use as animation parameters. They inherit directly from `parameterized_object` and are identified by the static class variable `VALUE_PROVIDER = true`.
|
||||
|
||||
### value_provider
|
||||
**Identifying Value Providers**: Use `animation.is_value_provider(obj)` to check if an object is a value provider. This function checks for the `VALUE_PROVIDER = true` static variable.
|
||||
|
||||
Base interface for all value providers. Inherits from `parameterized_object`.
|
||||
**Creating Custom Value Providers**: To create a custom value provider, inherit from `parameterized_object` and set `static var VALUE_PROVIDER = true`:
|
||||
|
||||
```berry
|
||||
class my_custom_provider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
|
||||
def produce_value(name, time_ms)
|
||||
# Return computed value based on time
|
||||
return computed_value
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Common Value Provider Interface
|
||||
|
||||
All value providers share these characteristics from `parameterized_object`:
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| *(none)* | - | - | - | Base interface has no parameters |
|
||||
| *(none specific)* | - | - | - | Value providers typically have no base parameters |
|
||||
|
||||
**Key Method**:
|
||||
- `produce_value(name, time_ms)` - Returns a value for the given parameter name at the specified time
|
||||
|
||||
**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)
|
||||
|
||||
### static_value
|
||||
|
||||
Wraps static values to provide value_provider interface. Inherits from `value_provider`.
|
||||
Wraps static values to provide value_provider interface. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
@@ -226,7 +250,7 @@ Wraps static values to provide value_provider interface. Inherits from `value_pr
|
||||
|
||||
### strip_length
|
||||
|
||||
Provides access to the LED strip length as a dynamic value. Inherits from `value_provider`.
|
||||
Provides access to the LED strip length as a dynamic value. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
@@ -238,7 +262,7 @@ Provides access to the LED strip length as a dynamic value. Inherits from `value
|
||||
|
||||
### oscillator_value
|
||||
|
||||
Generates oscillating values using various waveforms. Inherits from `value_provider`.
|
||||
Generates oscillating values using various waveforms. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
@@ -270,7 +294,7 @@ Generates oscillating values using various waveforms. Inherits from `value_provi
|
||||
|
||||
**⚠️ 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.
|
||||
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. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
@@ -335,11 +359,11 @@ animation pulse = breathe(
|
||||
|
||||
## Color Providers
|
||||
|
||||
Color providers generate dynamic colors over time, extending value_provider for color-specific functionality.
|
||||
Color providers generate dynamic colors over time. They inherit from `parameterized_object` with `VALUE_PROVIDER = true`, providing color-specific functionality while maintaining the value provider interface.
|
||||
|
||||
### color_provider
|
||||
|
||||
Base class for color providers that returns a solid color. Inherits from `value_provider`. Can be used directly for static colors or subclassed for dynamic color generation.
|
||||
Base class for color providers that returns a solid color. Inherits from `parameterized_object` with `VALUE_PROVIDER = true`. Can be used directly for static colors or subclassed for dynamic color generation.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
@@ -445,16 +469,16 @@ color fire_colors = rich_palette_color(
|
||||
|
||||
### breathe_color
|
||||
|
||||
Creates breathing/pulsing color effects by modulating the brightness of a base color over time. Inherits from `oscillator_value`.
|
||||
Creates breathing/pulsing color effects by modulating the brightness of a base color over time. Inherits from `color_provider` and uses an internal `oscillator_value` for time-based brightness modulation.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `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 |
|
||||
| `period` | 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 all oscillator_value parameters)* | | | | |
|
||||
| *(inherits color, brightness from color_provider)* | | | | |
|
||||
|
||||
**Curve Factor Effects:**
|
||||
- `1`: Pure cosine wave (smooth pulsing)
|
||||
@@ -471,7 +495,7 @@ color breathing_red = breathe_color(
|
||||
color=red,
|
||||
min_brightness=20,
|
||||
max_brightness=255,
|
||||
duration=4s,
|
||||
period=4s,
|
||||
curve_factor=3
|
||||
)
|
||||
|
||||
@@ -480,7 +504,7 @@ color pulse_blue = breathe_color(
|
||||
color=blue,
|
||||
min_brightness=50,
|
||||
max_brightness=200,
|
||||
duration=1s,
|
||||
period=1s,
|
||||
curve_factor=1
|
||||
)
|
||||
|
||||
@@ -489,7 +513,7 @@ color deep_breath = breathe_color(
|
||||
color=purple,
|
||||
min_brightness=5,
|
||||
max_brightness=255,
|
||||
duration=6s,
|
||||
period=6s,
|
||||
curve_factor=4
|
||||
)
|
||||
|
||||
@@ -499,7 +523,7 @@ color breathing_rainbow = breathe_color(
|
||||
color=rainbow_cycle,
|
||||
min_brightness=30,
|
||||
max_brightness=255,
|
||||
duration=3s,
|
||||
period=3s,
|
||||
curve_factor=2
|
||||
)
|
||||
```
|
||||
|
||||
@@ -380,7 +380,7 @@ color breathing_red = breathe_color(
|
||||
color=red
|
||||
min_brightness=5%
|
||||
max_brightness=100%
|
||||
duration=3s
|
||||
period=3s
|
||||
curve_factor=2
|
||||
)
|
||||
color pulsing_blue = breathe_color(
|
||||
@@ -388,7 +388,7 @@ color pulsing_blue = breathe_color(
|
||||
color=blue
|
||||
min_brightness=20%
|
||||
max_brightness=80%
|
||||
duration=1s
|
||||
period=1s
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ _detect_and_cache_symbol(name)
|
||||
│ ├── 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 animation.is_value_provider(instance) → create_value_provider()
|
||||
│ │ └── Check isinstance(instance, animation.animation) → create_animation()
|
||||
│ └── Cache result for future lookups
|
||||
└── Return nil if not found (handled as user-defined)
|
||||
@@ -371,7 +371,7 @@ _detect_and_cache_symbol(name)
|
||||
**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)
|
||||
# Detection: triangle(mock_engine) creates instance, animation.is_value_provider(instance)
|
||||
# Result: SymbolEntry("triangle", "value_provider", instance, true)
|
||||
# Reference: "animation.triangle"
|
||||
```
|
||||
@@ -442,8 +442,8 @@ MockEngine
|
||||
# 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)
|
||||
if animation.is_value_provider(instance)
|
||||
return SymbolEntry.create_value_provider(name, instance)
|
||||
end
|
||||
except .. as e, msg
|
||||
# Constructor failed - not a valid provider
|
||||
|
||||
@@ -50,12 +50,13 @@ class beacon : animation.animation
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Use virtual parameter access - automatically resolves value_providers
|
||||
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
|
||||
var right_edge = self.right_edge
|
||||
var member = self.member
|
||||
var back_color = member(self, "back_color")
|
||||
var pos = member(self, "pos")
|
||||
var slew_size = member(self, "slew_size")
|
||||
var beacon_size = member(self, "beacon_size")
|
||||
var color = member(self, "color")
|
||||
var right_edge = member(self, "right_edge")
|
||||
|
||||
# Fill background if not transparent
|
||||
if (back_color != 0xFF000000) && ((back_color & 0xFF000000) != 0x00)
|
||||
|
||||
@@ -12,7 +12,7 @@ import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class breathe : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var breathe_provider # Internal breathe color provider
|
||||
var _breathe # Internal breathe color provider
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
# Note: 'color' is inherited from Animation base class
|
||||
@@ -32,10 +32,10 @@ class breathe : animation.animation
|
||||
super(self).init(engine)
|
||||
|
||||
# Create internal breathe color provider
|
||||
self.breathe_provider = animation.breathe_color(engine)
|
||||
self._breathe = animation.breathe_color(engine)
|
||||
|
||||
# Set the animation's color parameter to use the breathe provider
|
||||
self.values["color"] = self.breathe_provider
|
||||
self.values["color"] = self._breathe
|
||||
end
|
||||
|
||||
# Handle parameter changes - propagate to internal breathe provider
|
||||
@@ -43,21 +43,21 @@ class breathe : animation.animation
|
||||
super(self).on_param_changed(name, value)
|
||||
# Propagate relevant parameters to the breathe provider
|
||||
if name == "color"
|
||||
# When color is set, update the breathe_provider's color
|
||||
# but keep the breathe_provider as the actual color source for rendering
|
||||
# When color is set, update the _breathe's color
|
||||
# but keep the _breathe as the actual color source for rendering
|
||||
if type(value) == 'int'
|
||||
self.breathe_provider.color = value
|
||||
# Restore the breathe_provider as the color source (bypass on_param_changed)
|
||||
self.values["color"] = self.breathe_provider
|
||||
self._breathe.color = value
|
||||
# Restore the _breathe as the color source (bypass on_param_changed)
|
||||
self.values["color"] = self._breathe
|
||||
end
|
||||
elif name == "min_brightness"
|
||||
self.breathe_provider.min_brightness = value
|
||||
self._breathe.min_brightness = value
|
||||
elif name == "max_brightness"
|
||||
self.breathe_provider.max_brightness = value
|
||||
self._breathe.max_brightness = value
|
||||
elif name == "period"
|
||||
self.breathe_provider.duration = value
|
||||
self._breathe.period = value
|
||||
elif name == "curve_factor"
|
||||
self.breathe_provider.curve_factor = value
|
||||
self._breathe.curve_factor = value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,17 +68,13 @@ class breathe : animation.animation
|
||||
def start(start_time)
|
||||
# Call parent start method first
|
||||
super(self).start(start_time)
|
||||
|
||||
# Start the breathe provider with the same time
|
||||
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
|
||||
self.breathe_provider.start(actual_start_time)
|
||||
|
||||
self._breathe.start(start_time)
|
||||
return self
|
||||
end
|
||||
|
||||
# The render method is inherited from Animation base class
|
||||
# It automatically uses self.color (which is set to self.breathe_provider)
|
||||
# The breathe_provider produces the breathing color effect
|
||||
# It automatically uses self.color (which is set to self._breathe)
|
||||
# The _breathe produces the breathing color effect
|
||||
end
|
||||
|
||||
return {'breathe': breathe }
|
||||
@@ -41,10 +41,12 @@ class comet : animation.animation
|
||||
if name == "direction"
|
||||
# Reset position when direction changes
|
||||
var strip_length = self.engine.strip_length
|
||||
if value > 0
|
||||
self.head_position = 0 # Start at beginning for forward movement
|
||||
else
|
||||
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
|
||||
if type(value) == 'int'
|
||||
if value > 0
|
||||
self.head_position = 0 # Start at beginning for forward movement
|
||||
else
|
||||
self.head_position = (strip_length - 1) * 256 # Start at end for backward movement
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -98,14 +98,6 @@ class crenel : animation.animation
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# NO setter/getter methods - use direct assignment instead:
|
||||
# obj.color = value
|
||||
# obj.back_color = value
|
||||
# obj.pos = value
|
||||
# obj.pulse_size = value
|
||||
# obj.low_size = value
|
||||
# obj.nb_pulse = value
|
||||
end
|
||||
|
||||
return {'crenel': crenel}
|
||||
|
||||
@@ -86,7 +86,7 @@ class engine_proxy : animation.animation
|
||||
if isinstance(obj, animation.sequence_manager)
|
||||
return self._add_sequence_manager(obj)
|
||||
# Check if it's a value_provider (before Animation check, as some animations might also be providers)
|
||||
elif isinstance(obj, animation.value_provider)
|
||||
elif animation.is_value_provider(obj)
|
||||
return self._add_value_provider(obj)
|
||||
# Check if it's an Animation (or subclass)
|
||||
elif isinstance(obj, animation.animation)
|
||||
@@ -226,7 +226,7 @@ class engine_proxy : animation.animation
|
||||
if isinstance(obj, animation.sequence_manager)
|
||||
return self._remove_sequence_manager(obj)
|
||||
# Check if it's a value_provider (before Animation check)
|
||||
elif isinstance(obj, animation.value_provider)
|
||||
elif animation.is_value_provider(obj)
|
||||
return self._remove_value_provider(obj)
|
||||
# Check if it's an Animation (or subclass)
|
||||
elif isinstance(obj, animation.animation)
|
||||
|
||||
@@ -21,7 +21,27 @@ class parameterized_object
|
||||
var engine # Reference to the animation engine
|
||||
var start_time # Time when object started (ms) (int), value is set at first call to update() or render()
|
||||
var is_running # Whether the object is active
|
||||
static var VALUE_PROVIDER = false # Set to true in value_provider subclasses
|
||||
|
||||
# Produce a value for a specific parameter name and time
|
||||
# This is the main method that value_provider subclasses should override
|
||||
#
|
||||
# `name` argument is generally ignored and the same value
|
||||
# is returned for any name, however this allows to have
|
||||
# special value providers that return coordinated distinct
|
||||
# values for different parameter names.
|
||||
#
|
||||
# For value providers, start is typically not called because instances
|
||||
# can be embedded in closures. So value providers must consider the first
|
||||
# call to `produce_value()` as a start of their internal time reference.
|
||||
#
|
||||
# @param name: string - Parameter name being requested
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - Value appropriate for the parameter type
|
||||
def produce_value(name, time_ms)
|
||||
return module("undefined") # Default behavior - return undefined
|
||||
end
|
||||
|
||||
# Initialize parameter system
|
||||
#
|
||||
# @param engine: AnimationEngine - Reference to the animation engine (required)
|
||||
|
||||
@@ -394,7 +394,7 @@ class SymbolTable
|
||||
var entry = animation_dsl._symbol_entry.create_color_constructor(name, instance, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
elif isinstance(instance, animation.value_provider)
|
||||
elif animation.is_value_provider(instance)
|
||||
var entry = animation_dsl._symbol_entry.create_value_provider_constructor(name, instance, true)
|
||||
self.entries[name] = entry
|
||||
return entry
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# Breathe Color Provider for Berry Animation Framework
|
||||
#
|
||||
# This color provider creates breathing/pulsing color effects by modulating the brightness
|
||||
# of a base color over time. It inherits from oscillator_value to leverage its
|
||||
# robust time management and waveform generation, then applies the oscillator value
|
||||
# as brightness modulation to a base color.
|
||||
# of a base color over time. It inherits from color_provider to leverage its color handling
|
||||
# and maintains an internal oscillator_value for time-based brightness modulation.
|
||||
#
|
||||
# The effect uses the oscillator's COSINE waveform with optional curve factor:
|
||||
# - curve_factor 1: Pure cosine wave (smooth pulsing)
|
||||
@@ -11,14 +10,17 @@
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class breathe_color : animation.oscillator_value
|
||||
# Additional parameter definitions for color-specific functionality
|
||||
# The oscillator parameters (min_value, max_value, duration, form, etc.) are inherited
|
||||
class breathe_color : animation.color_provider
|
||||
# Internal oscillator for brightness modulation
|
||||
var _oscillator
|
||||
|
||||
# Parameter definitions for breathing-specific functionality
|
||||
# The 'color' and 'brightness' parameters are inherited from color_provider
|
||||
static var PARAMS = animation.enc_params({
|
||||
"color": {"default": 0xFFFFFFFF}, # The base color to modulate (32-bit ARGB value)
|
||||
"min_brightness": {"min": 0, "max": 255, "default": 0}, # Minimum brightness level (0-255)
|
||||
"max_brightness": {"min": 0, "max": 255, "default": 255}, # Maximum brightness level (0-255)
|
||||
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
|
||||
"period": {"min": 1, "default": 3000}, # Breathing cycle time in ms (renamed from duration for consistency)
|
||||
"curve_factor": {"min": 1, "max": 5, "default": 2} # Factor to control breathing curve shape (1=cosine wave, 2-5=curved breathing with pauses)
|
||||
})
|
||||
|
||||
# Initialize a new Breathe Color Provider
|
||||
@@ -26,23 +28,23 @@ class breathe_color : animation.oscillator_value
|
||||
#
|
||||
# @param engine: AnimationEngine - The animation engine (required)
|
||||
def init(engine)
|
||||
# Call parent constructor (oscillator_value)
|
||||
# Call parent constructor (color_provider)
|
||||
super(self).init(engine)
|
||||
|
||||
# Configure the inherited oscillator for breathing behavior
|
||||
self.form = 4 #-animation.COSINE-# # Use cosine wave for smooth breathing
|
||||
self.min_value = 0 # Fixed range 0-255 for normalized oscillation
|
||||
self.max_value = 255 # Fixed range 0-255 for normalized oscillation
|
||||
self.duration = 3000 # Default duration
|
||||
# Create internal oscillator for brightness modulation
|
||||
self._oscillator = animation.oscillator_value(engine)
|
||||
self._oscillator.form = 4 #-animation.COSINE-# # Use cosine wave for smooth breathing
|
||||
self._oscillator.min_value = 0 # Fixed range 0-255 for normalized oscillation
|
||||
self._oscillator.max_value = 255 # Fixed range 0-255 for normalized oscillation
|
||||
self._oscillator.duration = 3000 # Default period (synced with our 'period' param)
|
||||
engine.add(self._oscillator) # register so it receives start()
|
||||
end
|
||||
|
||||
# Handle parameter changes - no need to sync oscillator min/max since they're fixed
|
||||
# Handle parameter changes - sync period to internal oscillator
|
||||
def on_param_changed(name, value)
|
||||
# Only handle curve_factor changes for oscillator form
|
||||
if name == "curve_factor"
|
||||
# For curve_factor = 1, use pure cosine
|
||||
# For curve_factor > 1, we'll apply the curve in produce_value
|
||||
self.form = 4 #-animation.COSINE-#
|
||||
# Sync period changes to the internal oscillator's duration
|
||||
if name == "period"
|
||||
self._oscillator.duration = value
|
||||
end
|
||||
|
||||
# Call parent's parameter change handler
|
||||
@@ -50,14 +52,14 @@ class breathe_color : animation.oscillator_value
|
||||
end
|
||||
|
||||
# Produce color value based on current time
|
||||
# This overrides the parent's produce_value to return colors instead of raw values
|
||||
# This overrides the parent's produce_value to return colors with modulated brightness
|
||||
#
|
||||
# @param name: string - Parameter name (ignored for color providers)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - 32-bit ARGB color value with modulated brightness
|
||||
def produce_value(name, time_ms)
|
||||
# Get the normalized oscillator value (0-255) from parent class
|
||||
var normalized_value = super(self).produce_value(name, time_ms)
|
||||
# Get the normalized oscillator value (0-255) from internal oscillator
|
||||
var normalized_value = self._oscillator.produce_value(name, time_ms)
|
||||
|
||||
# Apply curve factor if > 1 for natural breathing effect
|
||||
var current_curve_factor = self.curve_factor
|
||||
@@ -82,22 +84,11 @@ class breathe_color : animation.oscillator_value
|
||||
# Now map the curved value to the brightness range
|
||||
var brightness = tasmota.scale_uint(curved_value, 0, 255, self.min_brightness, self.max_brightness)
|
||||
|
||||
# Apply brightness to the base color (using inherited 'color' parameter)
|
||||
# Get the base color (inherited from color_provider)
|
||||
var current_color = self.color
|
||||
|
||||
# Extract RGB components
|
||||
var alpha = (current_color >> 24) & 0xFF
|
||||
var red = (current_color >> 16) & 0xFF
|
||||
var green = (current_color >> 8) & 0xFF
|
||||
var blue = current_color & 0xFF
|
||||
|
||||
# Apply brightness scaling using tasmota.scale_uint
|
||||
red = tasmota.scale_uint(red, 0, 255, 0, brightness)
|
||||
green = tasmota.scale_uint(green, 0, 255, 0, brightness)
|
||||
blue = tasmota.scale_uint(blue, 0, 255, 0, brightness)
|
||||
|
||||
# Reconstruct color
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue
|
||||
|
||||
# Apply brightness
|
||||
return self.apply_brightness(current_color, brightness)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class closure_value : animation.value_provider
|
||||
class closure_value : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
var _closure # We keep the closure as instance variable for faster dereferencing, in addition to PARAMS
|
||||
|
||||
# Static parameter definitions
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
# - Constructor takes only 'engine' parameter
|
||||
# - All other parameters set via virtual member assignment after creation
|
||||
|
||||
class color_provider : animation.value_provider
|
||||
class color_provider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# LUT (Lookup Table) management for color providers
|
||||
# Subclasses can use this to cache pre-computed colors for performance
|
||||
# If a subclass doesn't use a LUT, this remains nil
|
||||
@@ -80,6 +81,7 @@ class color_provider : animation.value_provider
|
||||
end
|
||||
|
||||
# Extract RGB components (preserve alpha channel)
|
||||
var alpha = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
@@ -90,7 +92,7 @@ class color_provider : animation.value_provider
|
||||
b = tasmota.scale_uint(b, 0, 255, 0, brightness)
|
||||
|
||||
# Reconstruct color with scaled brightness (preserve alpha)
|
||||
return (color & 0xFF000000) | (r << 16) | (g << 8) | b
|
||||
return (alpha << 24) | (r << 16) | (g << 8) | b
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class iteration_number : animation.value_provider
|
||||
class iteration_number : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# Produce the current iteration number from the animation engine
|
||||
#
|
||||
# @param name: string - Parameter name being requested (ignored)
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# oscillator_value for Berry Animation Framework
|
||||
#
|
||||
# This value provider generates oscillating values based on time using various waveforms.
|
||||
# It's based on the original Animate_oscillator class but adapted to work as a value_provider.
|
||||
# Generates oscillating values based on time using various waveforms.
|
||||
#
|
||||
# Supported waveforms:
|
||||
# - SAWTOOTH (1): Linear ramp from a to b
|
||||
# - TRIANGLE (2): Linear ramp from a to b, then back to a
|
||||
# - SQUARE (3): Square wave alternating between a and b
|
||||
# - COSINE (4): Smooth cosine wave from a to b
|
||||
# - SAWTOOTH (1): Linear ramp from min to max, then reset
|
||||
# - TRIANGLE (2): Linear ramp from min to max, then back to min
|
||||
# - SQUARE (3): Alternates between min and max (duty_cycle controls ratio)
|
||||
# - COSINE (4): Smooth cosine wave (starts at min, peaks at mid-cycle)
|
||||
# - SINE (5): Pure sine wave (starts at mid-value)
|
||||
# - EASE_IN (6): Quadratic acceleration (slow start, fast end)
|
||||
# - EASE_OUT (7): Quadratic deceleration (fast start, slow end)
|
||||
# - ELASTIC (8): Spring-like overshoot and oscillation
|
||||
# - BOUNCE (9): Ball-like bouncing with decreasing amplitude
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
@@ -23,7 +27,8 @@ var EASE_OUT = 7
|
||||
var ELASTIC = 8
|
||||
var BOUNCE = 9
|
||||
|
||||
class oscillator_value : animation.value_provider
|
||||
class oscillator_value : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# Non-parameter instance variables only
|
||||
var value # current calculated value
|
||||
|
||||
@@ -68,12 +73,13 @@ class oscillator_value : animation.value_provider
|
||||
# @return number - Calculated oscillator value
|
||||
def produce_value(name, time_ms)
|
||||
# Get parameter values using virtual member access
|
||||
var duration = self.duration
|
||||
var min_value = self.min_value
|
||||
var max_value = self.max_value
|
||||
var form = self.form
|
||||
var phase = self.phase
|
||||
var duty_cycle = self.duty_cycle
|
||||
var member = self.member
|
||||
var duration = member(self, "duration")
|
||||
var min_value = member(self, "min_value")
|
||||
var max_value = member(self, "max_value")
|
||||
var form = member(self, "form")
|
||||
var phase = member(self, "phase")
|
||||
var duty_cycle = member(self, "duty_cycle")
|
||||
var scale_uint = tasmota.scale_uint
|
||||
var scale_int = tasmota.scale_int
|
||||
|
||||
@@ -102,63 +108,61 @@ class oscillator_value : animation.value_provider
|
||||
end
|
||||
end
|
||||
|
||||
# Compute normalized value (0-255) based on waveform, then scale to min/max
|
||||
var v # normalized value 0-255
|
||||
# Compute value directly in min_value..max_value range
|
||||
var v
|
||||
var duty_mid = scale_uint(duty_cycle, 0, 255, 0, duration)
|
||||
|
||||
if form == 3 #-SQUARE-#
|
||||
self.value = past < duty_mid ? min_value : max_value
|
||||
return self.value
|
||||
v = past < duty_mid ? min_value : max_value
|
||||
elif form == 2 #-TRIANGLE-#
|
||||
if past < duty_mid
|
||||
v = scale_uint(past, 0, duty_mid - 1, 0, 255)
|
||||
v = scale_uint(past, 0, duty_mid - 1, min_value, max_value)
|
||||
else
|
||||
v = scale_uint(past, duty_mid, duration - 1, 255, 0)
|
||||
v = scale_uint(past, duty_mid, duration - 1, max_value, min_value)
|
||||
end
|
||||
elif form == 4 || form == 5 #-COSINE/SINE-#
|
||||
var angle = scale_uint(past, 0, duration - 1, 0, 32767)
|
||||
if form == 4 angle -= 8192 end # cosine phase shift
|
||||
v = scale_int(tasmota.sine_int(angle), -4096, 4096, 0, 255)
|
||||
v = scale_int(tasmota.sine_int(angle), -4096, 4096, min_value, max_value)
|
||||
elif form == 6 || form == 7 #-EASE_IN/EASE_OUT-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
if form == 6 # ease_in: t^2
|
||||
v = scale_int(t * t, 0, 65025, 0, 255)
|
||||
v = scale_int(t * t, 0, 65025, min_value, max_value)
|
||||
else # ease_out: 1-(1-t)^2
|
||||
var inv = 255 - t
|
||||
v = 255 - scale_int(inv * inv, 0, 65025, 0, 255)
|
||||
v = scale_int(65025 - inv * inv, 0, 65025, min_value, max_value)
|
||||
end
|
||||
elif form == 8 #-ELASTIC-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
if t == 0 self.value = min_value return self.value end
|
||||
if t == 255 self.value = max_value return self.value end
|
||||
if t == 0 return (self.value := min_value) end
|
||||
if t == 255 return (self.value := max_value) end
|
||||
var decay = scale_uint(255 - t, 0, 255, 255, 32)
|
||||
var osc = tasmota.sine_int(scale_uint(t, 0, 255, 0, 196602) % 32767)
|
||||
var offset = scale_int(osc * decay, -1044480, 1044480, -255, 255)
|
||||
self.value = min_value + scale_int(t, 0, 255, 0, max_value - min_value) + offset
|
||||
var val_range = max_value - min_value
|
||||
var offset = scale_int(osc * decay, -1044480, 1044480, -val_range, val_range)
|
||||
v = min_value + scale_int(t, 0, 255, 0, val_range) + offset
|
||||
# Clamp with 25% overshoot allowance
|
||||
var overshoot = (max_value - min_value) / 4
|
||||
if self.value > max_value + overshoot self.value = max_value + overshoot end
|
||||
if self.value < min_value - overshoot self.value = min_value - overshoot end
|
||||
return self.value
|
||||
var overshoot = val_range / 4
|
||||
if v > max_value + overshoot v = max_value + overshoot end
|
||||
if v < min_value - overshoot v = min_value - overshoot end
|
||||
elif form == 9 #-BOUNCE-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
var val_range = max_value - min_value
|
||||
if t < 128
|
||||
var s = scale_uint(t, 0, 127, 0, 255)
|
||||
v = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
|
||||
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range)
|
||||
elif t < 192
|
||||
var s = scale_uint(t - 128, 0, 63, 0, 255)
|
||||
v = scale_int(255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255), 0, 255, 0, 128)
|
||||
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range / 2)
|
||||
else
|
||||
var s = scale_uint(t - 192, 0, 63, 0, 255)
|
||||
var bv = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
|
||||
v = 255 - scale_int(255 - bv, 0, 255, 0, 64)
|
||||
v = max_value - scale_int((255-s)*(255-s), 0, 65025, 0, val_range / 4)
|
||||
end
|
||||
else #-SAWTOOTH (default)-#
|
||||
v = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
v = scale_uint(past, 0, duration - 1, min_value, max_value)
|
||||
end
|
||||
|
||||
self.value = scale_int(v, 0, 255, min_value, max_value)
|
||||
return self.value
|
||||
return (self.value := v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class static_value : animation.value_provider
|
||||
class static_value : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# Parameter definitions
|
||||
static var PARAMS = animation.enc_params({
|
||||
"value": {"default": nil, "type": "any"}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
# - Constructor takes only 'engine' parameter
|
||||
# - No additional parameters needed since strip length is obtained from engine
|
||||
|
||||
class strip_length : animation.value_provider
|
||||
class strip_length : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# Produce the strip length value
|
||||
#
|
||||
# @param name: string - Parameter name being requested (ignored)
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
# value_provider interface for Berry Animation Framework
|
||||
# value_provider utilities for Berry Animation Framework
|
||||
#
|
||||
# This defines the core interface for value providers in the animation framework.
|
||||
# Value providers generate values based on time, which can be used by animations
|
||||
# Value providers are parameterized_object subclasses with VALUE_PROVIDER = true.
|
||||
# They generate values based on time, which can be used by animations
|
||||
# for any parameter that needs to be dynamic over time.
|
||||
#
|
||||
# This is the super-class for all value provider variants and provides the interface
|
||||
# that animations can use to get dynamic values for their parameters.
|
||||
#
|
||||
# value_providers follow the parameterized class specification:
|
||||
# - Constructor takes only 'engine' parameter
|
||||
# - All other parameters set via virtual member assignment
|
||||
# - No setter/getter methods for parameters
|
||||
# The value_provider class has been removed from the hierarchy.
|
||||
# All value providers now inherit directly from parameterized_object
|
||||
# and set `static var VALUE_PROVIDER = true`.
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
class value_provider : animation.parameterized_object
|
||||
|
||||
# Produce a value for a specific parameter name and time
|
||||
# This is the main method that subclasses should override
|
||||
#
|
||||
# `name` argument is generally ignored and the same value
|
||||
# is returned for any name, however this allows to have
|
||||
# special value providers that return coordinated distinct
|
||||
# values for different parameter names.
|
||||
#
|
||||
# For value providers, start is typically not called because instances
|
||||
# can be embedded in closures. So value providers must consider the first
|
||||
# call to `produce_value()` as a start of their internal time reference.
|
||||
#
|
||||
# @param name: string - Parameter name being requested
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return any - Value appropriate for the parameter type
|
||||
def produce_value(name, time_ms)
|
||||
return module("undefined") # Default behavior - return undefined
|
||||
end
|
||||
end
|
||||
|
||||
# Add a method to check if an object is a value provider
|
||||
# Check if an object is a value provider
|
||||
# Returns true if obj is a parameterized_object with VALUE_PROVIDER = true
|
||||
def is_value_provider(obj)
|
||||
return isinstance(obj, animation.value_provider)
|
||||
return obj != nil && type(obj) == "instance" && isinstance(obj, animation.parameterized_object) && obj.VALUE_PROVIDER
|
||||
end
|
||||
|
||||
return {'value_provider': value_provider,
|
||||
'is_value_provider': is_value_provider}
|
||||
return {'is_value_provider': is_value_provider}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2271,7 +2271,7 @@ static const bvalue be_ktab_class_SymbolTable[55] = {
|
||||
/* K23 */ be_nested_str_weak(mock_engine),
|
||||
/* K24 */ be_nested_str_weak(color_provider),
|
||||
/* K25 */ be_nested_str_weak(create_color_constructor),
|
||||
/* K26 */ be_nested_str_weak(value_provider),
|
||||
/* K26 */ be_nested_str_weak(is_value_provider),
|
||||
/* K27 */ be_nested_str_weak(create_value_provider_constructor),
|
||||
/* K28 */ be_nested_str_weak(create_animation_constructor),
|
||||
/* K29 */ be_nested_str_weak(is_dangerous_call),
|
||||
@@ -2424,7 +2424,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
|
||||
&be_ktab_class_SymbolTable, /* shared constants */
|
||||
be_str_weak(_detect_and_cache_symbol),
|
||||
&be_const_str_solidified,
|
||||
( &(const binstruction[179]) { /* code */
|
||||
( &(const binstruction[178]) { /* code */
|
||||
0xA40A0400, // 0000 IMPORT R2 K2
|
||||
0x880C0107, // 0001 GETMBR R3 R0 K7
|
||||
0x8C0C0708, // 0002 GETMET R3 R3 K8
|
||||
@@ -2434,7 +2434,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
|
||||
0x880C0107, // 0006 GETMBR R3 R0 K7
|
||||
0x940C0601, // 0007 GETIDX R3 R3 R1
|
||||
0x80040600, // 0008 RET 1 R3
|
||||
0xA80200A1, // 0009 EXBLK 0 #00AC
|
||||
0xA80200A0, // 0009 EXBLK 0 #00AB
|
||||
0xA40E1200, // 000A IMPORT R3 K9
|
||||
0x8810050A, // 000B GETMBR R4 R2 K10
|
||||
0x8C100908, // 000C GETMET R4 R4 K8
|
||||
@@ -2495,7 +2495,7 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
|
||||
0xB81A1C00, // 0043 GETNGBL R6 K14
|
||||
0x5C1C0200, // 0044 MOVE R7 R1
|
||||
0x7C100600, // 0045 CALL R4 3
|
||||
0x7812005F, // 0046 JMPF R4 #00A7
|
||||
0x7812005E, // 0046 JMPF R4 #00A6
|
||||
0xB8121C00, // 0047 GETNGBL R4 K14
|
||||
0x88100801, // 0048 GETMBR R4 R4 R1
|
||||
0x60140004, // 0049 GETGBL R5 G4
|
||||
@@ -2531,8 +2531,8 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
|
||||
0x1C180B15, // 0067 EQ R6 R5 K21
|
||||
0x741A0001, // 0068 JMPT R6 #006B
|
||||
0x1C180B16, // 0069 EQ R6 R5 K22
|
||||
0x781A003B, // 006A JMPF R6 #00A7
|
||||
0xA8020036, // 006B EXBLK 0 #00A3
|
||||
0x781A003A, // 006A JMPF R6 #00A6
|
||||
0xA8020035, // 006B EXBLK 0 #00A2
|
||||
0x5C180800, // 006C MOVE R6 R4
|
||||
0x881C0117, // 006D GETMBR R7 R0 K23
|
||||
0x7C180200, // 006E CALL R6 1
|
||||
@@ -2552,58 +2552,57 @@ be_local_closure(class_SymbolTable__detect_and_cache_symbol, /* name */
|
||||
0x98200207, // 007C SETIDX R8 R1 R7
|
||||
0xA8040002, // 007D EXBLK 1 2
|
||||
0x80040E00, // 007E RET 1 R7
|
||||
0x70020020, // 007F JMP #00A1
|
||||
0x601C000F, // 0080 GETGBL R7 G15
|
||||
0x5C200C00, // 0081 MOVE R8 R6
|
||||
0xB8261C00, // 0082 GETNGBL R9 K14
|
||||
0x8824131A, // 0083 GETMBR R9 R9 K26
|
||||
0x7C1C0400, // 0084 CALL R7 2
|
||||
0x781E000A, // 0085 JMPF R7 #0091
|
||||
0x881C0503, // 0086 GETMBR R7 R2 K3
|
||||
0x8C1C0F1B, // 0087 GETMET R7 R7 K27
|
||||
0x5C240200, // 0088 MOVE R9 R1
|
||||
0x5C280C00, // 0089 MOVE R10 R6
|
||||
0x502C0200, // 008A LDBOOL R11 1 0
|
||||
0x7C1C0800, // 008B CALL R7 4
|
||||
0x88200107, // 008C GETMBR R8 R0 K7
|
||||
0x98200207, // 008D SETIDX R8 R1 R7
|
||||
0xA8040002, // 008E EXBLK 1 2
|
||||
0x80040E00, // 008F RET 1 R7
|
||||
0x7002000F, // 0090 JMP #00A1
|
||||
0x601C000F, // 0091 GETGBL R7 G15
|
||||
0x5C200C00, // 0092 MOVE R8 R6
|
||||
0xB8261C00, // 0093 GETNGBL R9 K14
|
||||
0x8824130E, // 0094 GETMBR R9 R9 K14
|
||||
0x7C1C0400, // 0095 CALL R7 2
|
||||
0x781E0009, // 0096 JMPF R7 #00A1
|
||||
0x881C0503, // 0097 GETMBR R7 R2 K3
|
||||
0x8C1C0F1C, // 0098 GETMET R7 R7 K28
|
||||
0x5C240200, // 0099 MOVE R9 R1
|
||||
0x5C280C00, // 009A MOVE R10 R6
|
||||
0x502C0200, // 009B LDBOOL R11 1 0
|
||||
0x7C1C0800, // 009C CALL R7 4
|
||||
0x88200107, // 009D GETMBR R8 R0 K7
|
||||
0x98200207, // 009E SETIDX R8 R1 R7
|
||||
0xA8040002, // 009F EXBLK 1 2
|
||||
0x80040E00, // 00A0 RET 1 R7
|
||||
0xA8040001, // 00A1 EXBLK 1 1
|
||||
0x70020003, // 00A2 JMP #00A7
|
||||
0xAC180002, // 00A3 CATCH R6 0 2
|
||||
0x7002001F, // 007F JMP #00A0
|
||||
0xB81E1C00, // 0080 GETNGBL R7 K14
|
||||
0x8C1C0F1A, // 0081 GETMET R7 R7 K26
|
||||
0x5C240C00, // 0082 MOVE R9 R6
|
||||
0x7C1C0400, // 0083 CALL R7 2
|
||||
0x781E000A, // 0084 JMPF R7 #0090
|
||||
0x881C0503, // 0085 GETMBR R7 R2 K3
|
||||
0x8C1C0F1B, // 0086 GETMET R7 R7 K27
|
||||
0x5C240200, // 0087 MOVE R9 R1
|
||||
0x5C280C00, // 0088 MOVE R10 R6
|
||||
0x502C0200, // 0089 LDBOOL R11 1 0
|
||||
0x7C1C0800, // 008A CALL R7 4
|
||||
0x88200107, // 008B GETMBR R8 R0 K7
|
||||
0x98200207, // 008C SETIDX R8 R1 R7
|
||||
0xA8040002, // 008D EXBLK 1 2
|
||||
0x80040E00, // 008E RET 1 R7
|
||||
0x7002000F, // 008F JMP #00A0
|
||||
0x601C000F, // 0090 GETGBL R7 G15
|
||||
0x5C200C00, // 0091 MOVE R8 R6
|
||||
0xB8261C00, // 0092 GETNGBL R9 K14
|
||||
0x8824130E, // 0093 GETMBR R9 R9 K14
|
||||
0x7C1C0400, // 0094 CALL R7 2
|
||||
0x781E0009, // 0095 JMPF R7 #00A0
|
||||
0x881C0503, // 0096 GETMBR R7 R2 K3
|
||||
0x8C1C0F1C, // 0097 GETMET R7 R7 K28
|
||||
0x5C240200, // 0098 MOVE R9 R1
|
||||
0x5C280C00, // 0099 MOVE R10 R6
|
||||
0x502C0200, // 009A LDBOOL R11 1 0
|
||||
0x7C1C0800, // 009B CALL R7 4
|
||||
0x88200107, // 009C GETMBR R8 R0 K7
|
||||
0x98200207, // 009D SETIDX R8 R1 R7
|
||||
0xA8040002, // 009E EXBLK 1 2
|
||||
0x80040E00, // 009F RET 1 R7
|
||||
0xA8040001, // 00A0 EXBLK 1 1
|
||||
0x70020003, // 00A1 JMP #00A6
|
||||
0xAC180002, // 00A2 CATCH R6 0 2
|
||||
0x70020000, // 00A3 JMP #00A5
|
||||
0x70020000, // 00A4 JMP #00A6
|
||||
0x70020000, // 00A5 JMP #00A7
|
||||
0xB0080000, // 00A6 RAISE 2 R0 R0
|
||||
0x4C100000, // 00A7 LDNIL R4
|
||||
0xA8040001, // 00A8 EXBLK 1 1
|
||||
0x80040800, // 00A9 RET 1 R4
|
||||
0xA8040001, // 00AA EXBLK 1 1
|
||||
0x70020005, // 00AB JMP #00B2
|
||||
0xAC0C0002, // 00AC CATCH R3 0 2
|
||||
0x70020002, // 00AD JMP #00B1
|
||||
0x4C140000, // 00AE LDNIL R5
|
||||
0x80040A00, // 00AF RET 1 R5
|
||||
0x70020000, // 00B0 JMP #00B2
|
||||
0xB0080000, // 00B1 RAISE 2 R0 R0
|
||||
0x80000000, // 00B2 RET 0
|
||||
0xB0080000, // 00A5 RAISE 2 R0 R0
|
||||
0x4C100000, // 00A6 LDNIL R4
|
||||
0xA8040001, // 00A7 EXBLK 1 1
|
||||
0x80040800, // 00A8 RET 1 R4
|
||||
0xA8040001, // 00A9 EXBLK 1 1
|
||||
0x70020005, // 00AA JMP #00B1
|
||||
0xAC0C0002, // 00AB CATCH R3 0 2
|
||||
0x70020002, // 00AC JMP #00B0
|
||||
0x4C140000, // 00AD LDNIL R5
|
||||
0x80040A00, // 00AE RET 1 R5
|
||||
0x70020000, // 00AF JMP #00B1
|
||||
0xB0080000, // 00B0 RAISE 2 R0 R0
|
||||
0x80000000, // 00B1 RET 0
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Test file for Breathe Color Provider
|
||||
#
|
||||
# This file contains tests for the breathe_color class following parameterized class specification
|
||||
# breathe_color now inherits from color_provider and uses an internal oscillator
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/breathe_color_provider_test.be
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end import animation import animation_dsl " lib/libesp32/berry_animation/src/tests/breathe_color_provider_test.be
|
||||
|
||||
print("Testing breathe_color...")
|
||||
|
||||
@@ -24,25 +25,24 @@ print("Created breathe color provider with defaults")
|
||||
print(f"Default color: 0x{provider.color :08x}")
|
||||
print(f"Default min_brightness: {provider.min_brightness}")
|
||||
print(f"Default max_brightness: {provider.max_brightness}")
|
||||
print(f"Default duration: {provider.duration}")
|
||||
print(f"Default period: {provider.period}")
|
||||
print(f"Default curve_factor: {provider.curve_factor}")
|
||||
print(f"Default form: {provider.form}")
|
||||
|
||||
# Verify inherited oscillator defaults
|
||||
print(f"Inherited min_value: {provider.min_value}")
|
||||
print(f"Inherited max_value: {provider.max_value}")
|
||||
# Verify it inherits from color_provider
|
||||
assert(animation.is_color_provider(provider), "breathe_color should be a color_provider")
|
||||
print("✓ breathe_color is a color_provider")
|
||||
|
||||
# Create another breathe color provider and set custom parameters using virtual member assignment
|
||||
var blue_breathe = animation.breathe_color(engine)
|
||||
blue_breathe.color = 0xFF0000FF
|
||||
blue_breathe.min_brightness = 20
|
||||
blue_breathe.max_brightness = 200
|
||||
blue_breathe.duration = 4000
|
||||
blue_breathe.period = 4000
|
||||
blue_breathe.curve_factor = 3
|
||||
print(f"Blue breathe color provider color: 0x{blue_breathe.color :08x}")
|
||||
print(f"Blue breathe color provider min_brightness: {blue_breathe.min_brightness}")
|
||||
print(f"Blue breathe color provider max_brightness: {blue_breathe.max_brightness}")
|
||||
print(f"Blue breathe color provider duration: {blue_breathe.duration}")
|
||||
print(f"Blue breathe color provider period: {blue_breathe.period}")
|
||||
print(f"Blue breathe color provider curve_factor: {blue_breathe.curve_factor}")
|
||||
|
||||
# Create red breathe color provider with different parameters
|
||||
@@ -50,18 +50,18 @@ var red_breathe = animation.breathe_color(engine)
|
||||
red_breathe.color = 0xFFFF0000
|
||||
red_breathe.min_brightness = 10
|
||||
red_breathe.max_brightness = 180
|
||||
red_breathe.duration = 3000
|
||||
red_breathe.period = 3000
|
||||
red_breathe.curve_factor = 2
|
||||
print(f"Red breathe color provider color: 0x{red_breathe.color :08x}")
|
||||
|
||||
# Test parameter updates using virtual member assignment
|
||||
blue_breathe.min_brightness = 30
|
||||
blue_breathe.max_brightness = 220
|
||||
blue_breathe.duration = 3500
|
||||
blue_breathe.period = 3500
|
||||
blue_breathe.curve_factor = 4
|
||||
print(f"Updated blue breathe min_brightness: {blue_breathe.min_brightness}")
|
||||
print(f"Updated blue breathe max_brightness: {blue_breathe.max_brightness}")
|
||||
print(f"Updated blue breathe duration: {blue_breathe.duration}")
|
||||
print(f"Updated blue breathe period: {blue_breathe.period}")
|
||||
print(f"Updated blue breathe curve_factor: {blue_breathe.curve_factor}")
|
||||
|
||||
# Test start method using engine time
|
||||
@@ -71,35 +71,35 @@ blue_breathe.start(start_time)
|
||||
blue_breathe.produce_value(nil, start_time) # force first tick
|
||||
print(f"Started blue breathe color provider at time: {start_time}")
|
||||
|
||||
# Cache duration for performance (following specification)
|
||||
var current_duration = blue_breathe.duration
|
||||
# Cache period for performance (following specification)
|
||||
var current_period = blue_breathe.period
|
||||
|
||||
# Test produce_value method at different points in the cycle
|
||||
engine.time_ms = start_time + (current_duration / 10)
|
||||
engine.time_ms = start_time + (current_period / 10)
|
||||
var color_1_10 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 1/10 cycle: 0x{color_1_10 :08x}")
|
||||
|
||||
engine.time_ms = start_time + (current_duration / 8)
|
||||
engine.time_ms = start_time + (current_period / 8)
|
||||
var color_1_8 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 1/8 cycle: 0x{color_1_8 :08x}")
|
||||
|
||||
engine.time_ms = start_time + (3 * current_duration / 10)
|
||||
engine.time_ms = start_time + (3 * current_period / 10)
|
||||
var color_3_10 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 3/10 cycle: 0x{color_3_10 :08x}")
|
||||
|
||||
engine.time_ms = start_time + (current_duration / 4)
|
||||
engine.time_ms = start_time + (current_period / 4)
|
||||
var color_1_4 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 1/4 cycle: 0x{color_1_4 :08x}")
|
||||
|
||||
engine.time_ms = start_time + (current_duration / 2)
|
||||
engine.time_ms = start_time + (current_period / 2)
|
||||
var color_1_2 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 1/2 cycle: 0x{color_1_2 :08x}")
|
||||
|
||||
engine.time_ms = start_time + (3 * current_duration / 4)
|
||||
engine.time_ms = start_time + (3 * current_period / 4)
|
||||
var color_3_4 = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at 3/4 cycle: 0x{color_3_4 :08x}")
|
||||
|
||||
engine.time_ms = start_time + current_duration
|
||||
engine.time_ms = start_time + current_period
|
||||
var color_full = blue_breathe.produce_value("color", engine.time_ms)
|
||||
print(f"Color at full cycle: 0x{color_full :08x}")
|
||||
|
||||
@@ -107,7 +107,7 @@ print(f"Color at full cycle: 0x{color_full :08x}")
|
||||
var curve_1_provider = animation.breathe_color(engine)
|
||||
curve_1_provider.color = 0xFF00FF00 # Green
|
||||
curve_1_provider.curve_factor = 1
|
||||
curve_1_provider.duration = 2000
|
||||
curve_1_provider.period = 2000
|
||||
curve_1_provider.min_brightness = 50 # Set non-zero minimum to see differences
|
||||
curve_1_provider.max_brightness = 255
|
||||
curve_1_provider.start(engine.time_ms)
|
||||
@@ -116,7 +116,7 @@ curve_1_provider.produce_value(nil, start_time) # force first tick
|
||||
var curve_5_provider = animation.breathe_color(engine)
|
||||
curve_5_provider.color = 0xFF00FF00 # Green
|
||||
curve_5_provider.curve_factor = 5
|
||||
curve_5_provider.duration = 2000
|
||||
curve_5_provider.period = 2000
|
||||
curve_5_provider.min_brightness = 50 # Set non-zero minimum to see differences
|
||||
curve_5_provider.max_brightness = 255
|
||||
curve_5_provider.start(engine.time_ms)
|
||||
@@ -164,7 +164,7 @@ var brightness_test = animation.breathe_color(engine)
|
||||
brightness_test.color = 0xFFFFFFFF # White
|
||||
brightness_test.min_brightness = 50
|
||||
brightness_test.max_brightness = 200
|
||||
brightness_test.duration = 1000
|
||||
brightness_test.period = 1000
|
||||
brightness_test.start(engine.time_ms)
|
||||
brightness_test.produce_value(nil, start_time) # force first tick
|
||||
|
||||
@@ -199,11 +199,8 @@ assert(red_breathe != nil, "Red breathe color provider should be created")
|
||||
assert(blue_breathe.color == 0xFF0000FF, "Blue breathe should have correct color")
|
||||
assert(blue_breathe.min_brightness == 30, "Min brightness should be updated to 30")
|
||||
assert(blue_breathe.max_brightness == 220, "Max brightness should be updated to 220")
|
||||
assert(blue_breathe.duration == 3500, "Duration should be updated to 3500")
|
||||
assert(blue_breathe.period == 3500, "Period should be updated to 3500")
|
||||
assert(blue_breathe.curve_factor == 4, "Curve factor should be updated to 4")
|
||||
assert(blue_breathe.form == 4 #-COSINE-#, "Form should be COSINE")
|
||||
assert(blue_breathe.min_value == 0, "Inherited min_value should be 0")
|
||||
assert(blue_breathe.max_value == 255, "Inherited max_value should be 255")
|
||||
assert(blue_breathe.engine == engine, "Provider should have correct engine reference")
|
||||
assert(alpha_actual == 128, "Alpha channel should be preserved")
|
||||
|
||||
@@ -219,4 +216,4 @@ assert(min_brightness_actual >= 45 && min_brightness_actual <= 65, "Min brightne
|
||||
assert(max_brightness_actual >= 150 && max_brightness_actual <= 170, "Max brightness should be around 160")
|
||||
|
||||
print("All tests completed successfully!")
|
||||
return true
|
||||
return true
|
||||
|
||||
@@ -79,7 +79,8 @@ def test_get_param_value_with_generic_provider()
|
||||
test_anim.opacity = 255
|
||||
|
||||
# Create a generic value_provider that we can track calls on
|
||||
class Trackingvalue_provider : animation.value_provider
|
||||
class Trackingvalue_provider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
var value
|
||||
var produce_value_called
|
||||
|
||||
@@ -125,7 +126,8 @@ def test_get_param_value_with_context_aware_provider()
|
||||
test_anim.opacity = 255
|
||||
|
||||
# Create a value_provider that returns different values based on parameter name
|
||||
class ContextAwareProvider : animation.value_provider
|
||||
class ContextAwareProvider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
var base_value
|
||||
var produce_value_called
|
||||
var last_param_name
|
||||
|
||||
@@ -162,7 +162,8 @@ def test_value_provider_as_parameter()
|
||||
var obj = TestClass(mock_engine)
|
||||
|
||||
# Create a mock value_provider
|
||||
class Mockvalue_provider : animation.value_provider
|
||||
class Mockvalue_provider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
var test_value
|
||||
def init(engine, value)
|
||||
super(self).init(engine)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Demonstration: RichPaletteColorProvider with dynamic brightness
|
||||
# Demonstration: rich_palette_color with dynamic brightness
|
||||
#
|
||||
# This demo shows how the LUT optimization now works correctly with
|
||||
# animations that have time-varying brightness, such as breathing effects.
|
||||
@@ -35,7 +35,7 @@ var rainbow_palette = bytes(
|
||||
)
|
||||
|
||||
# Create the color provider
|
||||
var provider = animation.rich_palette(engine)
|
||||
var provider = animation.rich_palette_color(engine)
|
||||
provider.colors = rainbow_palette
|
||||
provider.period = 0 # Value-based mode for gradient
|
||||
provider.brightness = 255
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Test for RichPaletteColorProvider dynamic brightness
|
||||
# Test for rich_palette_color dynamic brightness
|
||||
#
|
||||
# This test verifies that brightness can change over time without
|
||||
# invalidating the LUT cache, which is critical for animations
|
||||
@@ -14,7 +14,7 @@ end
|
||||
# Create a test engine
|
||||
var engine = animation.init_strip()
|
||||
|
||||
log("=== RichPaletteColorProvider Dynamic Brightness Test ===")
|
||||
log("=== rich_palette_color Dynamic Brightness Test ===")
|
||||
log("")
|
||||
|
||||
# Test 1: Verify brightness changes don't invalidate LUT
|
||||
@@ -28,7 +28,7 @@ var rgb_palette = bytes(
|
||||
"FFFFFF00" # Value 255: Yellow
|
||||
)
|
||||
|
||||
var provider = animation.rich_palette(engine)
|
||||
var provider = animation.rich_palette_color(engine)
|
||||
provider.colors = rgb_palette
|
||||
provider.period = 0 # Static mode
|
||||
|
||||
@@ -142,7 +142,7 @@ log("Test 4: LUT rebuild verification")
|
||||
log("---------------------------------")
|
||||
|
||||
# Create a fresh provider
|
||||
var rebuild_provider = animation.rich_palette(engine)
|
||||
var rebuild_provider = animation.rich_palette_color(engine)
|
||||
rebuild_provider.colors = rgb_palette
|
||||
rebuild_provider.period = 0
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Test for RichPaletteColorProvider LUT optimization
|
||||
# Test for rich_palette_color LUT optimization
|
||||
#
|
||||
# This test verifies that the LUT cache produces correct colors
|
||||
# and measures the performance improvement
|
||||
@@ -13,7 +13,7 @@ end
|
||||
# Create a test engine
|
||||
var engine = animation.init_strip()
|
||||
|
||||
log("=== RichPaletteColorProvider LUT Cache Test ===")
|
||||
log("=== rich_palette_color LUT Cache Test ===")
|
||||
log("")
|
||||
|
||||
# Test 1: Verify LUT produces correct colors
|
||||
@@ -41,7 +41,7 @@ while i < size(rainbow_palette)
|
||||
end
|
||||
log("")
|
||||
|
||||
var provider = animation.rich_palette(engine)
|
||||
var provider = animation.rich_palette_color(engine)
|
||||
provider.colors = rainbow_palette
|
||||
provider.period = 0 # Static mode for testing
|
||||
|
||||
@@ -96,7 +96,7 @@ log("Test 3: Performance measurement")
|
||||
log("-------------------------------")
|
||||
|
||||
# Create a fresh provider for performance testing
|
||||
var perf_provider = animation.rich_palette(engine)
|
||||
var perf_provider = animation.rich_palette_color(engine)
|
||||
perf_provider.colors = rainbow_palette
|
||||
perf_provider.period = 0
|
||||
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
# Test suite for value_provider base class
|
||||
# Test suite for value_provider functionality
|
||||
#
|
||||
# This test verifies that the base value_provider class works correctly
|
||||
# and follows the parameterized class specification with produce_value() API.
|
||||
# This test verifies that value providers work correctly
|
||||
# and follow the parameterized class specification with produce_value() API.
|
||||
# Note: value_provider class has been removed; providers now inherit from parameterized_object
|
||||
# with VALUE_PROVIDER = true.
|
||||
|
||||
import animation
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
# Simple test provider class (since value_provider class was removed)
|
||||
class TestValueProvider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
|
||||
def produce_value(name, time_ms)
|
||||
return module("undefined") # Default behavior
|
||||
end
|
||||
end
|
||||
|
||||
# Test the basic value_provider interface
|
||||
def test_value_provider_interface()
|
||||
print("Testing value_provider interface...")
|
||||
@@ -15,7 +26,7 @@ def test_value_provider_interface()
|
||||
var strip = global.Leds()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var provider = animation.value_provider(engine)
|
||||
var provider = TestValueProvider(engine)
|
||||
|
||||
# Test default produce_value method
|
||||
var result = provider.produce_value("test_param", 1000)
|
||||
@@ -37,7 +48,8 @@ def test_custom_value_provider()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create a simple time-based provider using new API
|
||||
class TimeBasedProvider : animation.value_provider
|
||||
class TimeBasedProvider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
# Parameter definitions
|
||||
static var PARAMS = animation.enc_params({
|
||||
"multiplier": {"default": 1}
|
||||
@@ -75,7 +87,7 @@ def test_is_value_provider()
|
||||
var strip = global.Leds()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var base_provider = animation.value_provider(engine)
|
||||
var base_provider = TestValueProvider(engine)
|
||||
|
||||
assert(animation.is_value_provider(base_provider) == true, "value_provider should be detected")
|
||||
assert(animation.is_value_provider(42) == false, "Integer should not be detected")
|
||||
@@ -93,7 +105,7 @@ def test_parameterized_object_integration()
|
||||
var strip = global.Leds()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var provider = animation.value_provider(engine)
|
||||
var provider = TestValueProvider(engine)
|
||||
|
||||
# Test that it has the engine reference
|
||||
assert(provider.engine != nil, "Provider should have engine reference")
|
||||
@@ -118,7 +130,8 @@ def test_lifecycle_methods()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create a provider that tracks start calls
|
||||
class LifecycleProvider : animation.value_provider
|
||||
class LifecycleProvider : animation.parameterized_object
|
||||
static var VALUE_PROVIDER = true
|
||||
var start_called
|
||||
var start_time
|
||||
|
||||
|
||||
Reference in New Issue
Block a user