"""LED picture target — sends a color strip source to an LED device.""" from dataclasses import dataclass from datetime import datetime from wled_controller.storage.picture_target import PictureTarget DEFAULT_STATE_CHECK_INTERVAL = 30 # seconds @dataclass class WledPictureTarget(PictureTarget): """LED picture target — pairs an LED device with a ColorStripSource. The ColorStripSource produces LED colors (calibration, color correction, smoothing). The target controls device-specific settings including send FPS. """ device_id: str = "" color_strip_source_id: str = "" fps: int = 30 # target send FPS (1-90) keepalive_interval: float = 1.0 # seconds between keepalive sends when screen is static state_check_interval: int = DEFAULT_STATE_CHECK_INTERVAL led_skip_start: int = 0 # first N LEDs forced to black led_skip_end: int = 0 # last M LEDs forced to black def register_with_manager(self, manager) -> None: """Register this WLED target with the processor manager.""" if self.device_id: manager.add_target( target_id=self.id, device_id=self.device_id, color_strip_source_id=self.color_strip_source_id, fps=self.fps, keepalive_interval=self.keepalive_interval, state_check_interval=self.state_check_interval, led_skip_start=self.led_skip_start, led_skip_end=self.led_skip_end, ) def sync_with_manager(self, manager, *, settings_changed: bool, source_changed: bool, device_changed: bool) -> None: """Push changed fields to the processor manager.""" if settings_changed: manager.update_target_settings(self.id, { "fps": self.fps, "keepalive_interval": self.keepalive_interval, "state_check_interval": self.state_check_interval, "led_skip_start": self.led_skip_start, "led_skip_end": self.led_skip_end, }) if source_changed: manager.update_target_color_strip_source(self.id, self.color_strip_source_id) if device_changed: manager.update_target_device(self.id, self.device_id) def update_fields(self, *, name=None, device_id=None, color_strip_source_id=None, fps=None, keepalive_interval=None, state_check_interval=None, led_skip_start=None, led_skip_end=None, description=None, **_kwargs) -> None: """Apply mutable field updates for WLED targets.""" super().update_fields(name=name, description=description) if device_id is not None: self.device_id = device_id if color_strip_source_id is not None: self.color_strip_source_id = color_strip_source_id if fps is not None: self.fps = fps if keepalive_interval is not None: self.keepalive_interval = keepalive_interval if state_check_interval is not None: self.state_check_interval = state_check_interval if led_skip_start is not None: self.led_skip_start = led_skip_start if led_skip_end is not None: self.led_skip_end = led_skip_end @property def has_picture_source(self) -> bool: return bool(self.color_strip_source_id) def to_dict(self) -> dict: """Convert to dictionary.""" d = super().to_dict() d["device_id"] = self.device_id d["color_strip_source_id"] = self.color_strip_source_id d["fps"] = self.fps d["keepalive_interval"] = self.keepalive_interval d["state_check_interval"] = self.state_check_interval d["led_skip_start"] = self.led_skip_start d["led_skip_end"] = self.led_skip_end return d @classmethod def from_dict(cls, data: dict) -> "WledPictureTarget": """Create from dictionary.""" return cls( id=data["id"], name=data["name"], target_type="led", device_id=data.get("device_id", ""), color_strip_source_id=data.get("color_strip_source_id", ""), fps=data.get("fps", 30), keepalive_interval=data.get("keepalive_interval", data.get("standby_interval", 1.0)), state_check_interval=data.get("state_check_interval", DEFAULT_STATE_CHECK_INTERVAL), led_skip_start=data.get("led_skip_start", 0), led_skip_end=data.get("led_skip_end", 0), description=data.get("description"), created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())), updated_at=datetime.fromisoformat(data.get("updated_at", datetime.utcnow().isoformat())), )