1
0

led_interface.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. """
  2. Unified LED interface for different LED control systems
  3. Provides a common abstraction layer for pattern manager integration.
  4. """
  5. import asyncio
  6. from typing import Optional, Literal
  7. from modules.led.led_controller import LEDController, effect_loading as wled_loading, effect_idle as wled_idle, effect_connected as wled_connected, effect_playing as wled_playing
  8. from modules.led.board_led_controller import BoardLEDController
  9. LEDProviderType = Literal["wled", "board", "none"]
  10. class LEDInterface:
  11. """
  12. Unified interface for LED control that works with multiple backends.
  13. Automatically delegates to the appropriate controller based on configuration.
  14. """
  15. def __init__(self, provider: LEDProviderType = "none", ip_address: Optional[str] = None,
  16. num_leds: Optional[int] = None, gpio_pin: Optional[int] = None, pixel_order: Optional[str] = None,
  17. brightness: Optional[float] = None, speed: Optional[int] = None, intensity: Optional[int] = None):
  18. self.provider = provider
  19. self._controller = None
  20. if provider == "wled" and ip_address:
  21. self._controller = LEDController(ip_address)
  22. elif provider == "board":
  23. # The table's own LED ring, driven by the FluidNC firmware.
  24. self._controller = BoardLEDController()
  25. @property
  26. def is_configured(self) -> bool:
  27. """Check if LED controller is configured"""
  28. return self._controller is not None
  29. def update_config(self, provider: LEDProviderType, ip_address: Optional[str] = None,
  30. num_leds: Optional[int] = None, gpio_pin: Optional[int] = None, pixel_order: Optional[str] = None,
  31. brightness: Optional[float] = None, speed: Optional[int] = None, intensity: Optional[int] = None):
  32. """Update LED provider configuration"""
  33. self.provider = provider
  34. # Stop existing controller if switching providers
  35. if self._controller and hasattr(self._controller, 'stop'):
  36. try:
  37. self._controller.stop()
  38. except:
  39. pass
  40. if provider == "wled" and ip_address:
  41. self._controller = LEDController(ip_address)
  42. elif provider == "board":
  43. self._controller = BoardLEDController()
  44. else:
  45. self._controller = None
  46. # NOTE: for the "board" provider the effect_* transition hooks below fall
  47. # through to False on purpose — the firmware switches run/idle effects
  48. # itself ($LED/RunEffect / $LED/IdleEffect); the host must not fight it.
  49. def effect_loading(self) -> bool:
  50. """Show loading effect"""
  51. if not self.is_configured:
  52. return False
  53. if self.provider == "wled":
  54. return wled_loading(self._controller)
  55. return False
  56. def effect_idle(self, effect_name: Optional[str] = None) -> bool:
  57. """Show idle effect"""
  58. if not self.is_configured:
  59. return False
  60. if self.provider == "wled":
  61. return wled_idle(self._controller)
  62. return False
  63. def effect_connected(self) -> bool:
  64. """Show connected effect"""
  65. if not self.is_configured:
  66. return False
  67. if self.provider == "wled":
  68. return wled_connected(self._controller)
  69. return False
  70. def effect_playing(self, effect_name: Optional[str] = None) -> bool:
  71. """Show playing effect"""
  72. if not self.is_configured:
  73. return False
  74. if self.provider == "wled":
  75. return wled_playing(self._controller)
  76. return False
  77. def set_power(self, state: int) -> dict:
  78. """Set power state (0=Off, 1=On, 2=Toggle)"""
  79. if not self.is_configured:
  80. return {"connected": False, "message": "No LED controller configured"}
  81. return self._controller.set_power(state)
  82. def check_status(self) -> dict:
  83. """Check controller status"""
  84. if not self.is_configured:
  85. return {"connected": False, "message": "No LED controller configured"}
  86. if self.provider == "wled":
  87. return self._controller.check_wled_status()
  88. elif self.provider == "board":
  89. return self._controller.check_status()
  90. return {"connected": False, "message": "Unknown provider"}
  91. def get_controller(self):
  92. """Get the underlying controller instance (for advanced usage)"""
  93. return self._controller
  94. # Async versions of methods for non-blocking calls from async context
  95. # These use asyncio.to_thread() to avoid blocking the event loop
  96. async def effect_loading_async(self) -> bool:
  97. """Show loading effect (non-blocking)"""
  98. return await asyncio.to_thread(self.effect_loading)
  99. async def effect_idle_async(self, effect_name: Optional[str] = None) -> bool:
  100. """Show idle effect (non-blocking)"""
  101. return await asyncio.to_thread(self.effect_idle, effect_name)
  102. async def effect_connected_async(self) -> bool:
  103. """Show connected effect (non-blocking)"""
  104. return await asyncio.to_thread(self.effect_connected)
  105. async def effect_playing_async(self, effect_name: Optional[str] = None) -> bool:
  106. """Show playing effect (non-blocking)"""
  107. return await asyncio.to_thread(self.effect_playing, effect_name)
  108. async def set_power_async(self, state: int) -> dict:
  109. """Set power state (non-blocking)"""
  110. return await asyncio.to_thread(self.set_power, state)
  111. async def check_status_async(self) -> dict:
  112. """Check controller status (non-blocking)"""
  113. return await asyncio.to_thread(self.check_status)