Update led_controller.py
__init__ was completely rewritten to be decoupled. It now receives loop, event_bus, config, and preferences directly and no longer has any reference to ServerState.
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Tuple
|
from typing import Any, Callable, Dict, Optional, Tuple
|
||||||
|
|
||||||
import board
|
import board
|
||||||
|
|
||||||
from .event_bus import EventHandler, subscribe
|
from .config import LedConfig
|
||||||
|
from .event_bus import EventBus, EventHandler, subscribe
|
||||||
|
from .models import Preferences
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,17 +23,15 @@ _PURPLE = (128, 0, 255)
|
|||||||
class LedController(EventHandler):
|
class LedController(EventHandler):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
state,
|
loop: asyncio.AbstractEventLoop,
|
||||||
led_type: str = "dotstar",
|
event_bus: EventBus,
|
||||||
interface: str = "spi",
|
config: LedConfig,
|
||||||
clock_pin: int = 13,
|
preferences: Preferences,
|
||||||
data_pin: int = 12,
|
|
||||||
num_leds: int = 3,
|
|
||||||
):
|
):
|
||||||
super().__init__(state)
|
super().__init__(event_bus)
|
||||||
self.loop = state.loop
|
self.loop = loop
|
||||||
self.num_leds = num_leds
|
self.num_leds = preferences.num_leds # Get num_leds from preferences
|
||||||
self.current_task = None
|
self.current_task: Optional[asyncio.Task] = None
|
||||||
self._is_ready = False
|
self._is_ready = False
|
||||||
self.leds = None
|
self.leds = None
|
||||||
|
|
||||||
@@ -44,9 +44,8 @@ class LedController(EventHandler):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Universal Hardware Initialization
|
if config.led_type == "neopixel":
|
||||||
if led_type == "neopixel":
|
if config.interface == "spi":
|
||||||
if interface == "spi":
|
|
||||||
import busio
|
import busio
|
||||||
import neopixel_spi
|
import neopixel_spi
|
||||||
_LOGGER.debug(f"Initializing {self.num_leds} NeoPixel LEDs on hardware SPI")
|
_LOGGER.debug(f"Initializing {self.num_leds} NeoPixel LEDs on hardware SPI")
|
||||||
@@ -54,27 +53,30 @@ class LedController(EventHandler):
|
|||||||
self.leds = neopixel_spi.NeoPixel_SPI(spi, self.num_leds, auto_write=False)
|
self.leds = neopixel_spi.NeoPixel_SPI(spi, self.num_leds, auto_write=False)
|
||||||
else: # GPIO
|
else: # GPIO
|
||||||
import neopixel
|
import neopixel
|
||||||
_LOGGER.debug(f"Initializing {self.num_leds} NeoPixel LEDs on GPIO data={data_pin}")
|
_LOGGER.debug(f"Initializing {self.num_leds} NeoPixel LEDs on GPIO data={config.data_pin}")
|
||||||
pin_object = getattr(board, f"D{data_pin}")
|
pin_object = getattr(board, f"D{config.data_pin}")
|
||||||
self.leds = neopixel.NeoPixel(pin_object, self.num_leds, auto_write=False)
|
self.leds = neopixel.NeoPixel(pin_object, self.num_leds, auto_write=False)
|
||||||
else: # dotstar
|
else: # dotstar
|
||||||
import adafruit_dotstar
|
import adafruit_dotstar
|
||||||
if interface == "gpio":
|
if config.interface == "gpio":
|
||||||
_LOGGER.debug(f"Initializing {self.num_leds} DotStar LEDs on GPIO data={data_pin}, clock={clock_pin}")
|
_LOGGER.debug(f"Initializing {self.num_leds} DotStar LEDs on GPIO data={config.data_pin}, clock={config.clock_pin}")
|
||||||
data_pin_obj = getattr(board, f"D{data_pin}")
|
data_pin_obj = getattr(board, f"D{config.data_pin}")
|
||||||
clock_pin_obj = getattr(board, f"D{clock_pin}")
|
clock_pin_obj = getattr(board, f"D{config.clock_pin}")
|
||||||
self.leds = adafruit_dotstar.DotStar(clock_pin_obj, data_pin_obj, self.num_leds, auto_write=False)
|
self.leds = adafruit_dotstar.DotStar(clock_pin_obj, data_pin_obj, self.num_leds, auto_write=False)
|
||||||
else: # SPI
|
else: # SPI
|
||||||
_LOGGER.debug(f"Initializing {self.num_leds} DotStar LEDs on hardware SPI")
|
_LOGGER.debug(f"Initializing {self.num_leds} DotStar LEDs on hardware SPI")
|
||||||
self.leds = adafruit_dotstar.DotStar(board.SCLK, board.MOSI, self.num_leds, auto_write=False)
|
self.leds = adafruit_dotstar.DotStar(board.SCLK, board.MOSI, self.num_leds, auto_write=False)
|
||||||
|
|
||||||
self._is_ready = True
|
self._is_ready = True
|
||||||
_LOGGER.info(f"LED Controller initialized for {self.num_leds} {led_type} LEDs.")
|
_LOGGER.info(f"LED Controller initialized for {self.num_leds} {config.led_type} LEDs.")
|
||||||
self.run_action("startup_sequence")
|
self.run_action("startup_sequence")
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
_LOGGER.exception("Failed to initialize LED controller. LEDs will be disabled.")
|
_LOGGER.exception("Failed to initialize LED controller. LEDs will be disabled.")
|
||||||
|
|
||||||
|
# Subscribe to events *after* __init__ is complete
|
||||||
|
self._subscribe_all_methods()
|
||||||
|
|
||||||
def run_action(self, action_method_name: str, *args: Any) -> None:
|
def run_action(self, action_method_name: str, *args: Any) -> None:
|
||||||
if not self._is_ready: return
|
if not self._is_ready: return
|
||||||
if self.current_task and not self.current_task.done(): self.current_task.cancel()
|
if self.current_task and not self.current_task.done(): self.current_task.cancel()
|
||||||
@@ -89,7 +91,7 @@ class LedController(EventHandler):
|
|||||||
|
|
||||||
if publish_state:
|
if publish_state:
|
||||||
config["state_name"] = state_name
|
config["state_name"] = state_name
|
||||||
self.state.event_bus.publish("publish_state_to_mqtt", config)
|
self.event_bus.publish("publish_state_to_mqtt", config)
|
||||||
|
|
||||||
async def startup_sequence(self, color=None, brightness=None): await self.blink(_GREEN, 1.0)
|
async def startup_sequence(self, color=None, brightness=None): await self.blink(_GREEN, 1.0)
|
||||||
async def off(self, color, brightness): self.leds.fill(_OFF); self.leds.show()
|
async def off(self, color, brightness): self.leds.fill(_OFF); self.leds.show()
|
||||||
@@ -134,33 +136,23 @@ class LedController(EventHandler):
|
|||||||
self.leds.fill(_OFF); self.leds[i % self.num_leds] = bright_color; self.leds.show(); i += 1; await asyncio.sleep(speed)
|
self.leds.fill(_OFF); self.leds[i % self.num_leds] = bright_color; self.leds.show(); i += 1; await asyncio.sleep(speed)
|
||||||
except asyncio.CancelledError: self.leds.fill(_OFF); self.leds.show()
|
except asyncio.CancelledError: self.leds.fill(_OFF); self.leds.show()
|
||||||
|
|
||||||
# --- MODIFIED STATE SUBSCRIPTIONS ---
|
# --- State Subscriptions ---
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def voice_idle(self, data: dict): self._apply_state_effect("idle")
|
def voice_idle(self, data: dict): self._apply_state_effect("idle")
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def voice_listen(self, data: dict): self._apply_state_effect("listening")
|
def voice_listen(self, data: dict): self._apply_state_effect("listening")
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def voice_thinking(self, data: dict): self._apply_state_effect("thinking")
|
def voice_thinking(self, data: dict): self._apply_state_effect("thinking")
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def voice_responding(self, data: dict): self._apply_state_effect("responding")
|
def voice_responding(self, data: dict): self._apply_state_effect("responding")
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def voice_error(self, data: dict): self._apply_state_effect("error")
|
def voice_error(self, data: dict): self._apply_state_effect("error")
|
||||||
|
|
||||||
# --- REMOVED REDUNDANT SUBSCRIPTIONS ---
|
|
||||||
# (e.g., voice_wakeword, voice_stt_end, etc.)
|
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def mic_muted(self, data: dict): self.run_action("solid", _DIM_RED, 1.0)
|
def mic_muted(self, data: dict): self.run_action("solid", _DIM_RED, 1.0)
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def mic_unmuted(self, data: dict): self._apply_state_effect("idle")
|
def mic_unmuted(self, data: dict): self._apply_state_effect("idle")
|
||||||
|
|
||||||
# --- MQTT CONFIG SUBSCRIPTIONS (Unchanged) ---
|
# --- MQTT Config Subscriptions ---
|
||||||
|
|
||||||
def _update_config(self, state_name: str, data: dict, apply: bool):
|
def _update_config(self, state_name: str, data: dict, apply: bool):
|
||||||
config = self.configs[state_name]; changed = False; is_retained = data.get("retained", False)
|
config = self.configs[state_name]; changed = False; is_retained = data.get("retained", False)
|
||||||
@@ -179,7 +171,7 @@ class LedController(EventHandler):
|
|||||||
return
|
return
|
||||||
if changed:
|
if changed:
|
||||||
if apply: self._apply_state_effect(state_name)
|
if apply: self._apply_state_effect(state_name)
|
||||||
else: config["state_name"] = state_name; self.state.event_bus.publish("publish_state_to_mqtt", config)
|
else: config["state_name"] = state_name; self.event_bus.publish("publish_state_to_mqtt", config)
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def set_idle_effect(self, data: dict): self._update_config("idle", data, True)
|
def set_idle_effect(self, data: dict): self._update_config("idle", data, True)
|
||||||
@@ -201,6 +193,7 @@ class LedController(EventHandler):
|
|||||||
def set_error_effect(self, data: dict): self._update_config("error", data, False)
|
def set_error_effect(self, data: dict): self._update_config("error", data, False)
|
||||||
@subscribe
|
@subscribe
|
||||||
def set_error_color(self, data: dict): self._update_config("error", data, False)
|
def set_error_color(self, data: dict): self._update_config("error", data, False)
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def set_num_leds(self, data: dict):
|
def set_num_leds(self, data: dict):
|
||||||
num_leds = data.get("num_leds")
|
num_leds = data.get("num_leds")
|
||||||
|
|||||||
Reference in New Issue
Block a user