Add static color for simple devices, change auto-shutdown to auto-restore
- Add `static_color` capability to Adalight provider with `set_color()` method
- Add `static_color` field to Device model, DeviceState, and API schemas
- Add GET/PUT `/devices/{id}/color` API endpoints
- Change auto-shutdown behavior: restore device to idle state instead of
powering off (WLED uses snapshot/restore, Adalight sends static color
or black frame)
- Rename `_auto_shutdown_device_if_idle` to `_restore_device_idle_state`
- Add inline color picker on device cards for devices with static_color
- Add auto_shutdown toggle to device settings modal
- Update labels from "Auto Shutdown" to "Auto Restore" (en + ru)
- Remove backward-compat KC aliases from ProcessorManager
- Align card action buttons to bottom with flex column layout
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ from wled_controller.api.schemas.devices import (
|
||||
DeviceUpdate,
|
||||
DiscoveredDeviceResponse,
|
||||
DiscoverDevicesResponse,
|
||||
StaticColorUpdate,
|
||||
)
|
||||
from wled_controller.core.capture.calibration import (
|
||||
calibration_from_dict,
|
||||
@@ -51,6 +52,7 @@ 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)),
|
||||
calibration=CalibrationSchema(**calibration_to_dict(device.calibration)),
|
||||
created_at=device.created_at,
|
||||
@@ -446,6 +448,66 @@ 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 color immediately
|
||||
if color is not None and not manager.is_device_processing(device_id):
|
||||
try:
|
||||
provider = get_provider(device.device_type)
|
||||
await provider.set_color(
|
||||
device.url, color,
|
||||
led_count=device.led_count, baud_rate=device.baud_rate,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to apply static color immediately: {e}")
|
||||
|
||||
return {"color": list(color) if color else None}
|
||||
|
||||
|
||||
# ===== CALIBRATION ENDPOINTS =====
|
||||
|
||||
@router.get("/api/v1/devices/{device_id}/calibration", response_model=CalibrationSchema, tags=["Calibration"])
|
||||
|
||||
@@ -28,6 +28,15 @@ 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."""
|
||||
|
||||
@@ -92,7 +101,8 @@ class DeviceResponse(BaseModel):
|
||||
led_count: int = Field(description="Total number of LEDs")
|
||||
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="Turn off device when server stops")
|
||||
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")
|
||||
calibration: Optional[Calibration] = Field(None, description="Calibration configuration")
|
||||
created_at: datetime = Field(description="Creation timestamp")
|
||||
|
||||
Reference in New Issue
Block a user