Add power toggle button to LED device cards

WLED: native on/off via JSON API. Adalight: sends all-black frame
to blank LEDs (uses existing client if target is running, otherwise
opens temporary serial connection). Toggle button placed next to
delete button in card top-right corner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 19:18:39 +03:00
parent f4503d36b4
commit cc91ccd75a
9 changed files with 193 additions and 4 deletions

View File

@@ -1,4 +1,4 @@
"""Device routes: CRUD, health state, brightness, calibration."""
"""Device routes: CRUD, health state, brightness, power, calibration."""
import httpx
from fastapi import APIRouter, HTTPException, Depends
@@ -367,6 +367,63 @@ async def set_device_brightness(
raise HTTPException(status_code=502, detail=f"Failed to reach device: {e}")
# ===== POWER ENDPOINTS =====
@router.get("/api/v1/devices/{device_id}/power", tags=["Settings"])
async def get_device_power(
device_id: str,
_auth: AuthRequired,
store: DeviceStore = Depends(get_device_store),
):
"""Get current power state from the device."""
device = store.get_device(device_id)
if not device:
raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
if "power_control" not in get_device_capabilities(device.device_type):
raise HTTPException(status_code=400, detail=f"Power control is not supported for {device.device_type} devices")
try:
provider = get_provider(device.device_type)
on = await provider.get_power(device.url)
return {"on": on}
except Exception as e:
logger.error(f"Failed to get power for {device_id}: {e}")
raise HTTPException(status_code=502, detail=f"Failed to reach device: {e}")
@router.put("/api/v1/devices/{device_id}/power", tags=["Settings"])
async def set_device_power(
device_id: str,
body: dict,
_auth: AuthRequired,
store: DeviceStore = Depends(get_device_store),
manager = Depends(get_processor_manager),
):
"""Turn device on or off."""
device = store.get_device(device_id)
if not device:
raise HTTPException(status_code=404, detail=f"Device {device_id} not found")
if "power_control" not in get_device_capabilities(device.device_type):
raise HTTPException(status_code=400, detail=f"Power control is not supported for {device.device_type} devices")
on = body.get("on")
if on is None or not isinstance(on, bool):
raise HTTPException(status_code=400, detail="'on' must be a boolean")
try:
if device.device_type == "adalight":
if not on:
await manager.send_black_frame(device_id)
# "on" is a no-op for Adalight — next processing frame lights them up
else:
provider = get_provider(device.device_type)
await provider.set_power(device.url, on)
return {"on": on}
except Exception as e:
logger.error(f"Failed to set power for {device_id}: {e}")
raise HTTPException(status_code=502, detail=f"Failed to reach device: {e}")
# ===== CALIBRATION ENDPOINTS =====
@router.get("/api/v1/devices/{device_id}/calibration", response_model=CalibrationSchema, tags=["Calibration"])