Source code for ble2wled.animation

"""Beacon animation and trail rendering.

This module handles animation of beacons moving across LED strips and
rendering motion trails for visual effects.

Example:
    Animate beacons with motion trails::

        from ble2wled.animation import BeaconRunner, add_trail
        from ble2wled.colors import ble_beacon_to_rgb

        runner = BeaconRunner(led_count=60)
        leds = [[0, 0, 0] for _ in range(60)]

        # Get next position for beacon and add trail
        pos = runner.next_position('beacon_1')
        color = ble_beacon_to_rgb('beacon_1', rssi=-50, life=0.8)
        add_trail(leds, pos, color, trail_length=10, fade_factor=0.75)
"""

from typing import Dict, List, Tuple


[docs] class BeaconRunner: """Manages animation positions for beacons moving along LED strip. Each beacon gets its own position on the LED strip. Calling next_position() increments the position, creating a moving effect across the strip. Attributes: led_count (int): Total number of LEDs in the strip. positions (dict): Current position of each beacon. """
[docs] def __init__(self, led_count: int): """Initialize beacon runner. Args: led_count (int): Total number of LEDs in the strip. Example: Create a beacon runner for a 60-LED strip:: runner = BeaconRunner(led_count=60) """ self.led_count = led_count self.positions: Dict[str, int] = {}
[docs] def next_position(self, beacon_id: str) -> int: """Get next position for a beacon. Increments position and wraps around at LED count. Maintains consistent position increment per beacon across calls. Args: beacon_id (str): Unique beacon identifier. Returns: int: Next position index (0 to led_count-1). Example: Get beacon positions for animation:: runner = BeaconRunner(led_count=60) pos1 = runner.next_position('beacon_1') # Returns 0 pos1 = runner.next_position('beacon_1') # Returns 1 pos2 = runner.next_position('beacon_2') # Returns 0 """ pos = self.positions.get(beacon_id, -1) pos = (pos + 1) % self.led_count self.positions[beacon_id] = pos return pos
[docs] def add_trail( leds: List[List[int]], position: int, color: Tuple[int, int, int], trail_length: int, fade_factor: float, ) -> None: """Add motion trail to LED array. Creates a fading trail effect behind the beacon by rendering progressively dimmer segments behind the main position. Uses additive blending to combine trail colors with existing LED values. Trail Rendering: - LED at position gets full color - Each previous LED gets color * fade_factor^distance - Colors are additively blended (clamped at 255) Args: leds (list): LED color array (list of [R, G, B] with values 0-255). position (int): Current beacon position index. color (tuple): RGB color tuple (R, G, B) with values 0-255. trail_length (int): Number of LEDs in the trail (including main position). fade_factor (float): Brightness decay per segment (0.0 to 1.0). 0.75 means each trailing segment is 75% brightness of previous. Raises: ValueError: If trail_length is negative or fade_factor outside 0-1. Example: Add a red trail to the LED array:: leds = [[0, 0, 0] for _ in range(60)] add_trail(leds, position=10, color=(255, 0, 0), trail_length=8, fade_factor=0.75) # Now leds[10] has max red, leds[9] has 75% red, etc. """ r, g, b = color for i in range(trail_length): idx = (position - i) % len(leds) fade = fade_factor**i leds[idx][0] = min(255, int(leds[idx][0] + r * fade)) leds[idx][1] = min(255, int(leds[idx][1] + g * fade)) leds[idx][2] = min(255, int(leds[idx][2] + b * fade))