diff --git a/server/src/wled_controller/api/routes/devices.py b/server/src/wled_controller/api/routes/devices.py index 642cd25..96f22cc 100644 --- a/server/src/wled_controller/api/routes/devices.py +++ b/server/src/wled_controller/api/routes/devices.py @@ -323,8 +323,9 @@ async def get_device_brightness( device_id: str, _auth: AuthRequired, store: DeviceStore = Depends(get_device_store), + manager: ProcessorManager = Depends(get_processor_manager), ): - """Get current brightness from the WLED device.""" + """Get current brightness from the device.""" device = store.get_device(device_id) if not device: raise HTTPException(status_code=404, detail=f"Device {device_id} not found") @@ -332,6 +333,8 @@ async def get_device_brightness( raise HTTPException(status_code=400, detail=f"Brightness control is not supported for {device.device_type} devices") try: + if device.device_type == "adalight": + return {"brightness": device.software_brightness} provider = get_provider(device.device_type) bri = await provider.get_brightness(device.url) return {"brightness": bri} @@ -346,8 +349,9 @@ async def set_device_brightness( body: dict, _auth: AuthRequired, store: DeviceStore = Depends(get_device_store), + manager: ProcessorManager = Depends(get_processor_manager), ): - """Set brightness on the WLED device directly.""" + """Set brightness on the device.""" device = store.get_device(device_id) if not device: raise HTTPException(status_code=404, detail=f"Device {device_id} not found") @@ -359,6 +363,14 @@ async def set_device_brightness( raise HTTPException(status_code=400, detail="brightness must be an integer 0-255") try: + if device.device_type == "adalight": + device.software_brightness = bri + device.updated_at = __import__("datetime").datetime.utcnow() + store.save() + # Update runtime state so the processing loop picks it up + if device_id in manager._devices: + manager._devices[device_id].software_brightness = bri + return {"brightness": bri} provider = get_provider(device.device_type) await provider.set_brightness(device.url, bri) return {"brightness": bri} diff --git a/server/src/wled_controller/core/adalight_provider.py b/server/src/wled_controller/core/adalight_provider.py index f8246b9..05b72f8 100644 --- a/server/src/wled_controller/core/adalight_provider.py +++ b/server/src/wled_controller/core/adalight_provider.py @@ -22,10 +22,10 @@ class AdalightDeviceProvider(LEDDeviceProvider): @property def capabilities(self) -> set: - # No hardware brightness control, no standby required # manual_led_count: user must specify LED count (can't auto-detect) # power_control: can blank LEDs by sending all-black pixels - return {"manual_led_count", "power_control"} + # brightness_control: software brightness (multiplies pixel values before sending) + return {"manual_led_count", "power_control", "brightness_control"} def create_client(self, url: str, **kwargs) -> LEDClient: from wled_controller.core.adalight_client import AdalightClient diff --git a/server/src/wled_controller/core/processor_manager.py b/server/src/wled_controller/core/processor_manager.py index 8f35d17..d51336b 100644 --- a/server/src/wled_controller/core/processor_manager.py +++ b/server/src/wled_controller/core/processor_manager.py @@ -166,6 +166,8 @@ class DeviceState: baud_rate: Optional[int] = None health: DeviceHealth = field(default_factory=DeviceHealth) health_task: Optional[asyncio.Task] = None + # Software brightness for devices without hardware brightness (e.g. Adalight) + software_brightness: int = 255 # 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) @@ -281,6 +283,7 @@ class ProcessorManager: calibration: Optional[CalibrationConfig] = None, device_type: str = "wled", baud_rate: Optional[int] = None, + software_brightness: int = 255, ): """Register a device for health monitoring. @@ -291,6 +294,7 @@ class ProcessorManager: calibration: Calibration config (creates default if None) device_type: LED device type (e.g. "wled") baud_rate: Serial baud rate (for adalight devices) + software_brightness: Software brightness 0-255 (for devices without hardware brightness) """ if device_id in self._devices: raise ValueError(f"Device {device_id} already registered") @@ -305,6 +309,7 @@ class ProcessorManager: calibration=calibration, device_type=device_type, baud_rate=baud_rate, + software_brightness=software_brightness, ) self._devices[device_id] = state @@ -775,6 +780,8 @@ class ProcessorManager: if not state.is_running or state.led_client is None: break brightness_value = int(led_brightness * 255) + if device_state and device_state.software_brightness < 255: + brightness_value = brightness_value * device_state.software_brightness // 255 if state.led_client.supports_fast_send: state.led_client.send_pixels_fast(state.previous_colors, brightness=brightness_value) else: @@ -803,6 +810,8 @@ class ProcessorManager: if not state.is_running or state.led_client is None: break brightness_value = int(led_brightness * 255) + if device_state and device_state.software_brightness < 255: + brightness_value = brightness_value * device_state.software_brightness // 255 t_send_start = time.perf_counter() if state.led_client.supports_fast_send: state.led_client.send_pixels_fast(led_colors, brightness=brightness_value) diff --git a/server/src/wled_controller/main.py b/server/src/wled_controller/main.py index 4eda1e2..ae1432a 100644 --- a/server/src/wled_controller/main.py +++ b/server/src/wled_controller/main.py @@ -157,6 +157,7 @@ async def lifespan(app: FastAPI): calibration=device.calibration, device_type=device.device_type, baud_rate=device.baud_rate, + software_brightness=device.software_brightness, ) logger.info(f"Registered device: {device.name} ({device.id})") except Exception as e: diff --git a/server/src/wled_controller/storage/device_store.py b/server/src/wled_controller/storage/device_store.py index 80cda8f..7fc3a7a 100644 --- a/server/src/wled_controller/storage/device_store.py +++ b/server/src/wled_controller/storage/device_store.py @@ -33,6 +33,7 @@ class Device: enabled: bool = True, device_type: str = "wled", baud_rate: Optional[int] = None, + software_brightness: int = 255, calibration: Optional[CalibrationConfig] = None, created_at: Optional[datetime] = None, updated_at: Optional[datetime] = None, @@ -44,6 +45,7 @@ class Device: self.enabled = enabled self.device_type = device_type self.baud_rate = baud_rate + self.software_brightness = software_brightness self.calibration = calibration or create_default_calibration(led_count) self.created_at = created_at or datetime.utcnow() self.updated_at = updated_at or datetime.utcnow() @@ -63,6 +65,8 @@ class Device: } if self.baud_rate is not None: d["baud_rate"] = self.baud_rate + if self.software_brightness != 255: + d["software_brightness"] = self.software_brightness return d @classmethod @@ -87,6 +91,7 @@ class Device: enabled=data.get("enabled", True), device_type=data.get("device_type", "wled"), baud_rate=data.get("baud_rate"), + software_brightness=data.get("software_brightness", 255), calibration=calibration, created_at=datetime.fromisoformat(data.get("created_at", datetime.utcnow().isoformat())), updated_at=datetime.fromisoformat(data.get("updated_at", datetime.utcnow().isoformat())),