__init__.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # -*- coding: utf-8 -*-
  2. """
  3. LED Animations-Framework.
  4. Definiert die IAnimation-Basisklasse und die AnimationRegistry
  5. fuer die Verwaltung verfuegbarer LED-Animationen.
  6. """
  7. from __future__ import annotations
  8. from abc import ABC, abstractmethod
  9. from collections.abc import Iterator
  10. from trixy_core.led.layouts import LEDLayout
  11. from trixy_core.utils.debug import pdebug, pwarn
  12. # RGB Tupel Type-Alias
  13. RGB = tuple[int, int, int]
  14. class IAnimation(ABC):
  15. """Basis-Klasse fuer alle LED-Animationen.
  16. Animationen erzeugen eine Folge von Frames als Generator.
  17. Jeder Frame ist eine Liste von RGB-Tupeln (ein Tupel pro LED).
  18. Klassen-Attribute:
  19. NAME: Registry-Name der Animation (z.B. "solid", "breathe").
  20. CONTINUOUS: True fuer endlose Animationen, False fuer einmalige.
  21. """
  22. NAME: str = ""
  23. CONTINUOUS: bool = False
  24. @abstractmethod
  25. def frames(
  26. self,
  27. led_count: int,
  28. color: RGB,
  29. speed: int,
  30. layout: LEDLayout,
  31. **opts: object,
  32. ) -> Iterator[list[RGB]]:
  33. """Generiert die Animation als Folge von Frames.
  34. Jeder Frame ist eine komplette Liste von RGB-Werten fuer
  35. alle LEDs. Der Aufrufer steuert das Timing zwischen den
  36. Frames anhand des speed-Parameters.
  37. Args:
  38. led_count: Anzahl der LEDs im Strip/Ring.
  39. color: Hauptfarbe als (R, G, B) Tupel.
  40. speed: Geschwindigkeit in Millisekunden pro Frame-Schritt.
  41. layout: LED-Layout mit ring_indices und center_index.
  42. **opts: Zusaetzliche Parameter je nach Animation
  43. (z.B. center_color, tail, direction).
  44. Yields:
  45. Frame als Liste von RGB-Tupeln, ein Tupel pro LED.
  46. Bei CONTINUOUS=True: endloser Generator (laeuft bis Abbruch).
  47. Bei CONTINUOUS=False: endliche Sequenz.
  48. """
  49. ...
  50. class AnimationRegistry:
  51. """Registry fuer verfuegbare LED-Animationen.
  52. Verwaltet eine Zuordnung von Animations-Namen zu ihren Klassen.
  53. Beim Start werden die eingebauten Animationen automatisch
  54. registriert, zusaetzliche koennen zur Laufzeit hinzugefuegt werden.
  55. """
  56. def __init__(self) -> None:
  57. self._animations: dict[str, type[IAnimation]] = {}
  58. def register(self, cls: type[IAnimation]) -> None:
  59. """Registriert eine Animations-Klasse.
  60. Verwendet den NAME-Klassenattribut als Schluessel.
  61. Warnt bei Ueberschreibung eines bereits registrierten Namens.
  62. Args:
  63. cls: Animations-Klasse (muss NAME gesetzt haben).
  64. """
  65. name = cls.NAME
  66. if not name:
  67. pwarn(
  68. f"Animation {cls.__name__} hat keinen NAME — "
  69. f"wird nicht registriert"
  70. )
  71. return
  72. if name in self._animations:
  73. pdebug(
  74. f"Animation '{name}' wird ueberschrieben: "
  75. f"{self._animations[name].__name__} → {cls.__name__}"
  76. )
  77. self._animations[name] = cls
  78. pdebug(f"Animation registriert: '{name}' ({cls.__name__})")
  79. def get(self, name: str) -> type[IAnimation] | None:
  80. """Gibt die Animations-Klasse fuer den angegebenen Namen zurueck.
  81. Args:
  82. name: Registry-Name der Animation.
  83. Returns:
  84. Animations-Klasse oder None wenn nicht gefunden.
  85. """
  86. return self._animations.get(name)
  87. def list_names(self) -> list[str]:
  88. """Gibt alle registrierten Animations-Namen zurueck.
  89. Returns:
  90. Sortierte Liste der verfuegbaren Animations-Namen.
  91. """
  92. return sorted(self._animations.keys())
  93. def register_builtins(self) -> None:
  94. """Registriert alle eingebauten Animationen.
  95. Importiert die Animations-Module und registriert alle
  96. darin definierten IAnimation-Unterklassen. Fehlende Module
  97. werden mit einer Warnung uebersprungen.
  98. """
  99. builtin_modules = [
  100. ".static",
  101. ".continuous",
  102. ".circular",
  103. ".effects",
  104. ]
  105. for module_name in builtin_modules:
  106. try:
  107. import importlib
  108. full_name = f"trixy_core.led.animations{module_name}"
  109. module = importlib.import_module(full_name)
  110. # Alle IAnimation-Unterklassen im Modul registrieren
  111. for attr_name in dir(module):
  112. attr = getattr(module, attr_name)
  113. if (
  114. isinstance(attr, type)
  115. and issubclass(attr, IAnimation)
  116. and attr is not IAnimation
  117. and attr.NAME
  118. ):
  119. self.register(attr)
  120. except ImportError:
  121. pdebug(
  122. f"Animations-Modul '{module_name}' nicht gefunden — "
  123. f"uebersprungen"
  124. )
  125. except Exception as e:
  126. pwarn(
  127. f"Fehler beim Laden von Animations-Modul "
  128. f"'{module_name}': {e}"
  129. )
  130. __all__ = [
  131. "RGB",
  132. "IAnimation",
  133. "AnimationRegistry",
  134. ]