diff --git a/server/src/wled_controller/api/routes/devices.py b/server/src/wled_controller/api/routes/devices.py index 88cc2ff..92ff800 100644 --- a/server/src/wled_controller/api/routes/devices.py +++ b/server/src/wled_controller/api/routes/devices.py @@ -22,7 +22,6 @@ from wled_controller.api.schemas.devices import ( DeviceUpdate, DiscoveredDeviceResponse, DiscoverDevicesResponse, - StaticColorUpdate, ) from wled_controller.core.processing.processor_manager import ProcessorManager from wled_controller.storage import DeviceStore @@ -45,7 +44,6 @@ def _device_to_response(device) -> DeviceResponse: enabled=device.enabled, baud_rate=device.baud_rate, auto_shutdown=device.auto_shutdown, - static_color=list(device.static_color) if device.static_color else None, capabilities=sorted(get_device_capabilities(device.device_type)), created_at=device.created_at, updated_at=device.updated_at, @@ -399,13 +397,6 @@ async def set_device_brightness( if ds: ds.hardware_brightness = bri - # If device is idle with a static color, re-send it at the new brightness - if ds and ds.static_color is not None and not manager.is_device_processing(device_id): - try: - await manager.send_static_color(device_id, ds.static_color) - except Exception: - pass - return {"brightness": bri} except Exception as e: logger.error(f"Failed to set brightness for {device_id}: {e}") @@ -465,11 +456,7 @@ async def set_device_power( # For serial devices, use the cached idle client to avoid port conflicts ds = manager._devices.get(device_id) if device.device_type in ("adalight", "ambiled") and ds: - if on: - # Restore idle state (static color or stay dark) - if ds.static_color is not None: - await manager.send_static_color(device_id, ds.static_color) - else: + if not on: await manager._send_clear_pixels(device_id) ds.power_on = on else: @@ -484,61 +471,3 @@ async def set_device_power( raise HTTPException(status_code=502, detail=f"Failed to reach device: {e}") -# ===== STATIC COLOR ENDPOINTS ===== - - -@router.get("/api/v1/devices/{device_id}/color", tags=["Settings"]) -async def get_device_color( - device_id: str, - _auth: AuthRequired, - store: DeviceStore = Depends(get_device_store), -): - """Get the static idle color for a device.""" - device = store.get_device(device_id) - if not device: - raise HTTPException(status_code=404, detail=f"Device {device_id} not found") - if "static_color" not in get_device_capabilities(device.device_type): - raise HTTPException(status_code=400, detail="Static color is not supported for this device type") - return {"color": list(device.static_color) if device.static_color else None} - - -@router.put("/api/v1/devices/{device_id}/color", tags=["Settings"]) -async def set_device_color( - device_id: str, - body: StaticColorUpdate, - _auth: AuthRequired, - store: DeviceStore = Depends(get_device_store), - manager: ProcessorManager = Depends(get_processor_manager), -): - """Set or clear the static idle color for a device.""" - device = store.get_device(device_id) - if not device: - raise HTTPException(status_code=404, detail=f"Device {device_id} not found") - if "static_color" not in get_device_capabilities(device.device_type): - raise HTTPException(status_code=400, detail="Static color is not supported for this device type") - - color = None - if body.color is not None: - if len(body.color) != 3 or not all(isinstance(c, int) and 0 <= c <= 255 for c in body.color): - raise HTTPException(status_code=400, detail="color must be [R, G, B] with values 0-255") - color = tuple(body.color) - - store.set_static_color(device_id, color) - - # Update runtime state - ds = manager._devices.get(device_id) - if ds: - ds.static_color = color - - # If device is idle, apply the change immediately - if not manager.is_device_processing(device_id): - try: - if color is not None: - await manager.send_static_color(device_id, color) - else: - await manager.clear_device(device_id) - except Exception as e: - logger.warning(f"Failed to apply color change immediately: {e}") - - return {"color": list(color) if color else None} - diff --git a/server/src/wled_controller/api/schemas/devices.py b/server/src/wled_controller/api/schemas/devices.py index dac9c92..dd2f9df 100644 --- a/server/src/wled_controller/api/schemas/devices.py +++ b/server/src/wled_controller/api/schemas/devices.py @@ -28,15 +28,6 @@ class DeviceUpdate(BaseModel): auto_shutdown: Optional[bool] = Field(None, description="Turn off device when server stops") -class StaticColorUpdate(BaseModel): - """Request to set or clear the static idle color.""" - - color: Optional[List[int]] = Field( - None, - description="RGB color [R, G, B] with values 0-255, or null to clear", - ) - - class Calibration(BaseModel): """Calibration configuration for pixel-to-LED mapping.""" @@ -102,7 +93,6 @@ class DeviceResponse(BaseModel): enabled: bool = Field(description="Whether device is enabled") baud_rate: Optional[int] = Field(None, description="Serial baud rate") auto_shutdown: bool = Field(default=False, description="Restore device to idle state when targets stop") - static_color: Optional[List[int]] = Field(None, description="Static idle color [R, G, B]") capabilities: List[str] = Field(default_factory=list, description="Device type capabilities") created_at: datetime = Field(description="Creation timestamp") updated_at: datetime = Field(description="Last update timestamp") diff --git a/server/src/wled_controller/core/devices/adalight_client.py b/server/src/wled_controller/core/devices/adalight_client.py index b5d7646..d781679 100644 --- a/server/src/wled_controller/core/devices/adalight_client.py +++ b/server/src/wled_controller/core/devices/adalight_client.py @@ -100,7 +100,14 @@ class AdalightClient(LEDClient): raise RuntimeError(f"Failed to open serial port {self._port}: {e}") async def close(self) -> None: - """Close the serial port.""" + """Send black frame and close the serial port.""" + if self._connected and self._serial and self._serial.is_open and self._led_count > 0: + try: + black = np.zeros((self._led_count, 3), dtype=np.uint8) + frame = self._build_frame(black, brightness=255) + await asyncio.to_thread(self._serial.write, frame) + except Exception as e: + logger.debug(f"Failed to send black frame on close: {e}") self._connected = False if self._serial and self._serial.is_open: try: diff --git a/server/src/wled_controller/core/devices/serial_provider.py b/server/src/wled_controller/core/devices/serial_provider.py index 23c581b..fd32930 100644 --- a/server/src/wled_controller/core/devices/serial_provider.py +++ b/server/src/wled_controller/core/devices/serial_provider.py @@ -2,10 +2,10 @@ Subclasses only need to override ``device_type`` and ``create_client()``. All common serial-device logic (COM port validation, discovery, health -checks, power control via black frames, static colour) lives here. +checks, power control via black frames) lives here. """ -from typing import List, Tuple +from typing import List import numpy as np @@ -28,8 +28,7 @@ class SerialDeviceProvider(LEDDeviceProvider): # manual_led_count: user must specify LED count (can't auto-detect) # power_control: can blank LEDs by sending all-black pixels # brightness_control: software brightness (multiplies pixel values before sending) - # static_color: can send a solid colour frame - return {"manual_led_count", "power_control", "brightness_control", "static_color"} + return {"manual_led_count", "power_control", "brightness_control"} async def check_health(self, url: str, http_client, prev_health=None) -> DeviceHealth: # Generic serial port health check — enumerate COM ports @@ -116,31 +115,3 @@ class SerialDeviceProvider(LEDDeviceProvider): finally: await client.close() - async def set_color(self, url: str, color: Tuple[int, int, int], **kwargs) -> None: - """Send a solid color frame to the device. - - Accepts optional kwargs: - client: An already-connected LEDClient (e.g. cached idle client). - brightness (int): Software brightness 0-255 (default 255). - led_count (int), baud_rate (int | None). - """ - led_count = kwargs.get("led_count", 0) - if led_count <= 0: - raise ValueError(f"led_count is required to send color frame to {self.device_type} device") - - brightness = kwargs.get("brightness", 255) - frame = np.full((led_count, 3), color, dtype=np.uint8) - - existing_client = kwargs.get("client") - if existing_client: - await existing_client.send_pixels(frame, brightness=brightness) - else: - baud_rate = kwargs.get("baud_rate") - client = self.create_client(url, led_count=led_count, baud_rate=baud_rate) - try: - await client.connect() - await client.send_pixels(frame, brightness=brightness) - finally: - await client.close() - - logger.info(f"{self.device_type} set_color: sent solid {color} to {url}") diff --git a/server/src/wled_controller/core/processing/processor_manager.py b/server/src/wled_controller/core/processing/processor_manager.py index 1820af4..9f86eac 100644 --- a/server/src/wled_controller/core/processing/processor_manager.py +++ b/server/src/wled_controller/core/processing/processor_manager.py @@ -47,8 +47,6 @@ class DeviceState: hardware_brightness: Optional[int] = None # Auto-restore: restore device to idle state when targets stop auto_shutdown: bool = False - # Static idle color for devices without a rich editor (e.g. Adalight) - static_color: Optional[Tuple[int, int, int]] = None # Calibration test mode (works independently of target processing) test_mode_active: bool = False test_mode_edges: Dict[str, Tuple[int, int, int]] = field(default_factory=dict) @@ -159,7 +157,6 @@ class ProcessorManager: baud_rate: Optional[int] = None, software_brightness: int = 255, auto_shutdown: bool = False, - static_color: Optional[Tuple[int, int, int]] = None, ): """Register a device for health monitoring.""" if device_id in self._devices: @@ -173,7 +170,6 @@ class ProcessorManager: baud_rate=baud_rate, software_brightness=software_brightness, auto_shutdown=auto_shutdown, - static_color=static_color, ) self._devices[device_id] = state @@ -634,22 +630,6 @@ class ProcessorManager: return proc.device_id return None - async def send_static_color(self, device_id: str, color: Tuple[int, int, int]) -> None: - """Send a solid color to a device via its provider.""" - ds = self._devices.get(device_id) - if not ds: - raise ValueError(f"Device {device_id} not found") - try: - provider = get_provider(ds.device_type) - client = await self._get_idle_client(device_id) - await provider.set_color( - ds.device_url, color, - led_count=ds.led_count, baud_rate=ds.baud_rate, client=client, - brightness=ds.software_brightness, - ) - except Exception as e: - logger.error(f"Failed to send static color for {device_id}: {e}") - async def clear_device(self, device_id: str) -> None: """Clear LED output on a device (send black / power off).""" ds = self._devices.get(device_id) @@ -663,9 +643,8 @@ class ProcessorManager: async def _restore_device_idle_state(self, device_id: str) -> None: """Restore a device to its idle state when all targets stop. - - If a static color is configured, send it. - For WLED: do nothing — stop() already restored the snapshot. - - For other devices without static color: power off (black frame). + - For other devices: power off (send black frame). """ ds = self._devices.get(device_id) if not ds or not ds.auto_shutdown: @@ -675,14 +654,7 @@ class ProcessorManager: return try: - if ds.static_color is not None: - await self.send_static_color(device_id, ds.static_color) - logger.info( - f"Auto-restore: sent static color {ds.static_color} " - f"to {ds.device_type} device {device_id}" - ) - elif ds.device_type != "wled": - # Non-WLED without static color: power off (send black frame) + if ds.device_type != "wled": await self._send_clear_pixels(device_id) logger.info(f"Auto-restore: powered off {ds.device_type} device {device_id}") else: diff --git a/server/src/wled_controller/main.py b/server/src/wled_controller/main.py index 2b9dbd0..1f3333d 100644 --- a/server/src/wled_controller/main.py +++ b/server/src/wled_controller/main.py @@ -213,7 +213,6 @@ async def lifespan(app: FastAPI): baud_rate=device.baud_rate, software_brightness=device.software_brightness, auto_shutdown=device.auto_shutdown, - static_color=device.static_color, ) logger.info(f"Registered device: {device.name} ({device.id})") except Exception as e: diff --git a/server/src/wled_controller/static/css/cards.css b/server/src/wled_controller/static/css/cards.css index 48dcdc5..1941903 100644 --- a/server/src/wled_controller/static/css/cards.css +++ b/server/src/wled_controller/static/css/cards.css @@ -423,39 +423,6 @@ section { } /* Static color picker — inline in card-subtitle */ -.static-color-control { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.static-color-picker { - width: 22px; - height: 18px; - padding: 0; - border: 1px solid var(--border-color); - border-radius: 3px; - cursor: pointer; - background: none; - vertical-align: middle; -} - -.btn-clear-color { - background: none; - border: none; - color: #777; - font-size: 0.75rem; - cursor: pointer; - padding: 0 2px; - line-height: 1; - border-radius: 3px; - transition: color 0.2s; -} - -.btn-clear-color:hover { - color: var(--danger-color); -} - .section-header { display: flex; align-items: center; diff --git a/server/src/wled_controller/static/js/app.js b/server/src/wled_controller/static/js/app.js index 76a8db2..9977368 100644 --- a/server/src/wled_controller/static/js/app.js +++ b/server/src/wled_controller/static/js/app.js @@ -30,8 +30,7 @@ import { import { showSettings, closeDeviceSettingsModal, forceCloseDeviceSettingsModal, saveDeviceSettings, updateBrightnessLabel, saveCardBrightness, - saveDeviceStaticColor, clearDeviceStaticColor, - toggleDevicePower, removeDevice, loadDevices, + turnOffDevice, removeDevice, loadDevices, updateSettingsBaudFpsHint, } from './features/devices.js'; import { @@ -149,9 +148,7 @@ Object.assign(window, { saveDeviceSettings, updateBrightnessLabel, saveCardBrightness, - saveDeviceStaticColor, - clearDeviceStaticColor, - toggleDevicePower, + turnOffDevice, removeDevice, loadDevices, updateSettingsBaudFpsHint, diff --git a/server/src/wled_controller/static/js/features/devices.js b/server/src/wled_controller/static/js/features/devices.js index a97845a..755a4c5 100644 --- a/server/src/wled_controller/static/js/features/devices.js +++ b/server/src/wled_controller/static/js/features/devices.js @@ -69,7 +69,7 @@ export function createDeviceCard(device) { return `