| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- # -*- coding: utf-8 -*-
- """
- Kontinuierliche LED-Animationen (Endlosschleife, CONTINUOUS = True).
- Enthaelt Blink-, Atmen- und Puls-Animationen die endlos wiederholt werden.
- """
- from __future__ import annotations
- import math
- from collections.abc import Iterator
- from trixy_core.led.animations import IAnimation, RGB
- from trixy_core.led.layouts import LEDLayout
- # Schwarz / aus
- OFF: RGB = (0, 0, 0)
- # Schritte pro Zyklus
- BREATHE_STEPS = 60
- PULSE_STEPS = 30
- class BlinkAnimation(IAnimation):
- """Endloses Blinken: an/aus im Wechsel.
- Bei Jewel: Center-LED bleibt bei center_color (wenn angegeben),
- Ring-LEDs blinken normal.
- """
- NAME = "blink"
- CONTINUOUS = True
- def frames(
- self,
- led_count: int,
- color: RGB,
- speed: int,
- layout: LEDLayout,
- **opts: object,
- ) -> Iterator[list[RGB]]:
- center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
- on_frame = [color] * led_count
- if layout.center_index is not None and center_color is not None:
- on_frame[layout.center_index] = center_color
- # Aus-Frame: Center behalt center_color wenn gesetzt
- off_frame = [OFF] * led_count
- if layout.center_index is not None and center_color is not None:
- off_frame[layout.center_index] = center_color
- while True:
- yield list(on_frame)
- yield list(off_frame)
- class BreatheAnimation(IAnimation):
- """Sanftes Atmen mit Sinuskurve.
- Helligkeit folgt einer Sinuswelle ueber 60 Frames pro Zyklus
- (0% → 100% → 0%). Bei Jewel: Center atmet mit halber Intensitaet.
- """
- NAME = "breathe"
- CONTINUOUS = True
- def frames(
- self,
- led_count: int,
- color: RGB,
- speed: int,
- layout: LEDLayout,
- **opts: object,
- ) -> Iterator[list[RGB]]:
- center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
- while True:
- for step in range(BREATHE_STEPS):
- # Sinuskurve: 0 → 1 → 0 ueber einen Zyklus
- brightness = (math.sin(2 * math.pi * step / BREATHE_STEPS - math.pi / 2) + 1) / 2
- frame = [_scale_color(color, brightness)] * led_count
- # Jewel-Center: halbe Intensitaet
- if layout.center_index is not None:
- base = center_color if center_color is not None else color
- frame[layout.center_index] = _scale_color(base, brightness * 0.5)
- yield frame
- class PulseAnimation(IAnimation):
- """Schneller Puls mit Dreieckswelle.
- Abrupter als Breathe, verwendet eine lineare Dreieckswelle
- statt einer Sinuskurve. 30 Frames pro Zyklus.
- """
- NAME = "pulse"
- CONTINUOUS = True
- def frames(
- self,
- led_count: int,
- color: RGB,
- speed: int,
- layout: LEDLayout,
- **opts: object,
- ) -> Iterator[list[RGB]]:
- center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
- while True:
- for step in range(PULSE_STEPS):
- # Dreieckswelle: 0 → 1 (erste Haelfte), 1 → 0 (zweite Haelfte)
- half = PULSE_STEPS // 2
- if step < half:
- brightness = step / half
- else:
- brightness = (PULSE_STEPS - step) / half
- frame = [_scale_color(color, brightness)] * led_count
- # Jewel-Center: gleiche Dreieckswelle, halbe Intensitaet
- if layout.center_index is not None:
- base = center_color if center_color is not None else color
- frame[layout.center_index] = _scale_color(base, brightness * 0.5)
- yield frame
- # ── Hilfsfunktionen ──────────────────────────────────────────────────────
- def _scale_color(color: RGB, factor: float) -> RGB:
- """Skaliert eine RGB-Farbe um einen Faktor (0.0 - 1.0).
- Args:
- color: Ausgangsfarbe als (R, G, B).
- factor: Helligkeitsfaktor zwischen 0.0 und 1.0.
- Returns:
- Skalierte RGB-Farbe mit geclampten Werten.
- """
- return (
- min(255, max(0, int(color[0] * factor))),
- min(255, max(0, int(color[1] * factor))),
- min(255, max(0, int(color[2] * factor))),
- )
- def get_animations() -> list[type[IAnimation]]:
- """Gibt alle kontinuierlichen Animationsklassen zurueck."""
- return [
- BlinkAnimation,
- BreatheAnimation,
- PulseAnimation,
- ]
|