Some checks failed
Validate / validate (push) Failing after 1m6s
This is a complete WLED ambient lighting controller that captures screen border pixels and sends them to WLED devices for immersive ambient lighting effects. ## Server Features: - FastAPI-based REST API with 17+ endpoints - Real-time screen capture with multi-monitor support - Advanced LED calibration system with visual GUI - API key authentication with labeled tokens - Per-device brightness control (0-100%) - Configurable FPS (1-60), border width, and color correction - Persistent device storage (JSON-based) - Comprehensive Web UI with dark/light themes - Docker support with docker-compose - Windows monitor name detection via WMI (shows "LG ULTRAWIDE" etc.) ## Web UI Features: - Device management (add, configure, remove WLED devices) - Real-time status monitoring with FPS metrics - Settings modal for device configuration - Visual calibration GUI with edge testing - Brightness slider per device - Display selection with friendly monitor names - Token-based authentication with login/logout - Responsive button layout ## Calibration System: - Support for any LED strip layout (clockwise/counterclockwise) - 4 starting position options (corners) - Per-edge LED count configuration - Visual preview with starting position indicator - Test buttons to light up individual edges - Smart LED ordering based on start position and direction ## Home Assistant Integration: - Custom HACS integration - Switch entities for processing control - Sensor entities for status and FPS - Select entities for display selection - Config flow for easy setup - Auto-discovery of devices from server ## Technical Stack: - Python 3.11+ - FastAPI + uvicorn - mss (screen capture) - httpx (async WLED client) - Pydantic (validation) - WMI (Windows monitor detection) - Structlog (logging) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
"""Switch platform for WLED Screen Controller."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeassistant.components.switch import SwitchEntity
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
|
|
from .const import DOMAIN, ATTR_DEVICE_ID
|
|
from .coordinator import WLEDScreenControllerCoordinator
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up WLED Screen Controller switches."""
|
|
data = hass.data[DOMAIN][entry.entry_id]
|
|
coordinator: WLEDScreenControllerCoordinator = data["coordinator"]
|
|
|
|
entities = []
|
|
if coordinator.data and "devices" in coordinator.data:
|
|
for device_id, device_data in coordinator.data["devices"].items():
|
|
entities.append(
|
|
WLEDScreenControllerSwitch(
|
|
coordinator, device_id, device_data["info"], entry.entry_id
|
|
)
|
|
)
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class WLEDScreenControllerSwitch(CoordinatorEntity, SwitchEntity):
|
|
"""Representation of a WLED Screen Controller processing switch."""
|
|
|
|
_attr_has_entity_name = True
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: WLEDScreenControllerCoordinator,
|
|
device_id: str,
|
|
device_info: dict[str, Any],
|
|
entry_id: str,
|
|
) -> None:
|
|
"""Initialize the switch."""
|
|
super().__init__(coordinator)
|
|
self._device_id = device_id
|
|
self._device_info = device_info
|
|
self._entry_id = entry_id
|
|
|
|
self._attr_unique_id = f"{device_id}_processing"
|
|
self._attr_name = "Processing"
|
|
self._attr_icon = "mdi:television-ambient-light"
|
|
|
|
@property
|
|
def device_info(self) -> dict[str, Any]:
|
|
"""Return device information."""
|
|
return {
|
|
"identifiers": {(DOMAIN, self._device_id)},
|
|
}
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
"""Return true if processing is active."""
|
|
if not self.coordinator.data:
|
|
return False
|
|
|
|
device_data = self.coordinator.data["devices"].get(self._device_id)
|
|
if not device_data or not device_data.get("state"):
|
|
return False
|
|
|
|
return device_data["state"].get("processing", False)
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
"""Return if entity is available."""
|
|
if not self.coordinator.data:
|
|
return False
|
|
|
|
device_data = self.coordinator.data["devices"].get(self._device_id)
|
|
return device_data is not None
|
|
|
|
@property
|
|
def extra_state_attributes(self) -> dict[str, Any]:
|
|
"""Return additional state attributes."""
|
|
if not self.coordinator.data:
|
|
return {}
|
|
|
|
device_data = self.coordinator.data["devices"].get(self._device_id)
|
|
if not device_data:
|
|
return {}
|
|
|
|
state = device_data.get("state", {})
|
|
metrics = device_data.get("metrics", {})
|
|
|
|
attrs = {
|
|
ATTR_DEVICE_ID: self._device_id,
|
|
}
|
|
|
|
if state:
|
|
attrs["fps_target"] = state.get("fps_target")
|
|
attrs["fps_actual"] = state.get("fps_actual")
|
|
attrs["display_index"] = state.get("display_index")
|
|
|
|
if metrics:
|
|
attrs["frames_processed"] = metrics.get("frames_processed")
|
|
attrs["errors_count"] = metrics.get("errors_count")
|
|
attrs["uptime_seconds"] = metrics.get("uptime_seconds")
|
|
|
|
return attrs
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn on processing."""
|
|
try:
|
|
await self.coordinator.start_processing(self._device_id)
|
|
except Exception as err:
|
|
_LOGGER.error("Failed to start processing: %s", err)
|
|
raise
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn off processing."""
|
|
try:
|
|
await self.coordinator.stop_processing(self._device_id)
|
|
except Exception as err:
|
|
_LOGGER.error("Failed to stop processing: %s", err)
|
|
raise
|