static.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. # -*- coding: utf-8 -*-
  2. """
  3. Statische LED-Animationen (einmalig, CONTINUOUS = False).
  4. Enthaelt einfache Animationen wie Solid, Blink-Varianten, Flash
  5. und sequentielles Fuellen.
  6. """
  7. from __future__ import annotations
  8. from collections.abc import Iterator
  9. from trixy_core.led.animations import IAnimation, RGB
  10. from trixy_core.led.layouts import LEDLayout
  11. # Schwarz / aus
  12. OFF: RGB = (0, 0, 0)
  13. class SolidAnimation(IAnimation):
  14. """Alle LEDs auf eine Farbe setzen (1 Frame).
  15. Bei Jewel-Layouts wird die Center-LED optional mit einer
  16. separaten Farbe (center_color) angesteuert.
  17. """
  18. NAME = "solid"
  19. CONTINUOUS = False
  20. def frames(
  21. self,
  22. led_count: int,
  23. color: RGB,
  24. speed: int,
  25. layout: LEDLayout,
  26. **opts: object,
  27. ) -> Iterator[list[RGB]]:
  28. center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
  29. frame = [color] * led_count
  30. # Jewel: Center-LED separat einfaerben
  31. if layout.center_index is not None and center_color is not None:
  32. frame[layout.center_index] = center_color
  33. yield frame
  34. class SingleBlinkAnimation(IAnimation):
  35. """Einmaliges Blinken: an → aus."""
  36. NAME = "single-blink"
  37. CONTINUOUS = False
  38. def frames(
  39. self,
  40. led_count: int,
  41. color: RGB,
  42. speed: int,
  43. layout: LEDLayout,
  44. **opts: object,
  45. ) -> Iterator[list[RGB]]:
  46. yield from _blink_frames(led_count, color, layout, 1, **opts)
  47. class DoubleBlinkAnimation(IAnimation):
  48. """Zweimaliges Blinken: an/aus/an/aus."""
  49. NAME = "double-blink"
  50. CONTINUOUS = False
  51. def frames(
  52. self,
  53. led_count: int,
  54. color: RGB,
  55. speed: int,
  56. layout: LEDLayout,
  57. **opts: object,
  58. ) -> Iterator[list[RGB]]:
  59. yield from _blink_frames(led_count, color, layout, 2, **opts)
  60. class TripleBlinkAnimation(IAnimation):
  61. """Dreimaliges Blinken: an/aus/an/aus/an/aus."""
  62. NAME = "triple-blink"
  63. CONTINUOUS = False
  64. def frames(
  65. self,
  66. led_count: int,
  67. color: RGB,
  68. speed: int,
  69. layout: LEDLayout,
  70. **opts: object,
  71. ) -> Iterator[list[RGB]]:
  72. yield from _blink_frames(led_count, color, layout, 3, **opts)
  73. class FlashAnimation(IAnimation):
  74. """Kurzer heller Blitz (50 ms an, dann aus).
  75. Aehnlich wie Blink, aber nur ein einzelner schneller Frame.
  76. Die Engine verwendet speed=50 fuer den an-Frame.
  77. """
  78. NAME = "flash"
  79. CONTINUOUS = False
  80. def frames(
  81. self,
  82. led_count: int,
  83. color: RGB,
  84. speed: int,
  85. layout: LEDLayout,
  86. **opts: object,
  87. ) -> Iterator[list[RGB]]:
  88. # Heller Frame — volle Helligkeit
  89. on_frame = [color] * led_count
  90. off_frame = [OFF] * led_count
  91. yield on_frame
  92. yield off_frame
  93. class FillSequentialAnimation(IAnimation):
  94. """LEDs schalten nacheinander ein (Ring-Reihenfolge).
  95. Bei Jewel: Ring-LEDs zuerst, Center-LED zuletzt.
  96. Jeder Frame fuegt eine weitere LED hinzu.
  97. """
  98. NAME = "fill-sequential"
  99. CONTINUOUS = False
  100. def frames(
  101. self,
  102. led_count: int,
  103. color: RGB,
  104. speed: int,
  105. layout: LEDLayout,
  106. **opts: object,
  107. ) -> Iterator[list[RGB]]:
  108. center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
  109. # Reihenfolge: Ring-LEDs zuerst, dann Center (falls vorhanden)
  110. fill_order: list[int] = list(layout.ring_indices)
  111. if layout.center_index is not None:
  112. fill_order.append(layout.center_index)
  113. frame = [OFF] * led_count
  114. for idx in fill_order:
  115. # Center-LED bekommt eigene Farbe wenn angegeben
  116. if idx == layout.center_index and center_color is not None:
  117. frame[idx] = center_color
  118. else:
  119. frame[idx] = color
  120. # Kopie yielden, damit spaetere Aenderungen den Frame nicht
  121. # nachtraeglich veraendern
  122. yield list(frame)
  123. # ── Hilfsfunktionen ──────────────────────────────────────────────────────
  124. def _blink_frames(
  125. led_count: int,
  126. color: RGB,
  127. layout: LEDLayout,
  128. count: int,
  129. **opts: object,
  130. ) -> Iterator[list[RGB]]:
  131. """Erzeugt Blink-Frames (an/aus) fuer die angegebene Anzahl.
  132. Args:
  133. led_count: Gesamtanzahl LEDs.
  134. color: Primaerfarbe.
  135. layout: LED-Layout.
  136. count: Anzahl Blink-Zyklen.
  137. **opts: Zusaetzliche Optionen (center_color).
  138. Yields:
  139. Abwechselnd an- und aus-Frames.
  140. """
  141. center_color: RGB | None = opts.get("center_color") # type: ignore[assignment]
  142. on_frame = [color] * led_count
  143. if layout.center_index is not None and center_color is not None:
  144. on_frame[layout.center_index] = center_color
  145. off_frame = [OFF] * led_count
  146. for _ in range(count):
  147. yield list(on_frame)
  148. yield list(off_frame)
  149. def get_animations() -> list[type[IAnimation]]:
  150. """Gibt alle statischen Animationsklassen zurueck."""
  151. return [
  152. SolidAnimation,
  153. SingleBlinkAnimation,
  154. DoubleBlinkAnimation,
  155. TripleBlinkAnimation,
  156. FlashAnimation,
  157. FillSequentialAnimation,
  158. ]