- Remove light.py platform (static color control via HA light entity) - Add select.py with CSS Source and Brightness Source dropdowns for LED targets - Coordinator now fetches color-strip-sources and value-sources lists - Add generic update_target() method for partial target updates - Clean up stale device registry entries on integration reload - Skip frame sends when effective brightness is ~0 (suppresses unnecessary UDP/HTTP traffic while LEDs are dark) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
184 lines
6.0 KiB
Python
184 lines
6.0 KiB
Python
"""Select platform for LED Screen Controller (CSS source & brightness source)."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeassistant.components.select import SelectEntity
|
|
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, DATA_COORDINATOR, TARGET_TYPE_KEY_COLORS
|
|
from .coordinator import WLEDScreenControllerCoordinator
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
NONE_OPTION = "— None —"
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up LED Screen Controller select entities."""
|
|
data = hass.data[DOMAIN][entry.entry_id]
|
|
coordinator: WLEDScreenControllerCoordinator = data[DATA_COORDINATOR]
|
|
|
|
entities: list[SelectEntity] = []
|
|
if coordinator.data and "targets" in coordinator.data:
|
|
for target_id, target_data in coordinator.data["targets"].items():
|
|
info = target_data["info"]
|
|
|
|
# Only LED targets
|
|
if info.get("target_type") == TARGET_TYPE_KEY_COLORS:
|
|
continue
|
|
|
|
entities.append(
|
|
CSSSourceSelect(coordinator, target_id, entry.entry_id)
|
|
)
|
|
entities.append(
|
|
BrightnessSourceSelect(coordinator, target_id, entry.entry_id)
|
|
)
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class CSSSourceSelect(CoordinatorEntity, SelectEntity):
|
|
"""Select entity for choosing a color strip source for an LED target."""
|
|
|
|
_attr_has_entity_name = True
|
|
_attr_icon = "mdi:palette"
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: WLEDScreenControllerCoordinator,
|
|
target_id: str,
|
|
entry_id: str,
|
|
) -> None:
|
|
super().__init__(coordinator)
|
|
self._target_id = target_id
|
|
self._entry_id = entry_id
|
|
self._attr_unique_id = f"{target_id}_css_source"
|
|
self._attr_translation_key = "color_strip_source"
|
|
|
|
@property
|
|
def device_info(self) -> dict[str, Any]:
|
|
return {"identifiers": {(DOMAIN, self._target_id)}}
|
|
|
|
@property
|
|
def options(self) -> list[str]:
|
|
if not self.coordinator.data:
|
|
return []
|
|
sources = self.coordinator.data.get("css_sources") or []
|
|
return [s["name"] for s in sources]
|
|
|
|
@property
|
|
def current_option(self) -> str | None:
|
|
if not self.coordinator.data:
|
|
return None
|
|
target_data = self.coordinator.data.get("targets", {}).get(self._target_id)
|
|
if not target_data:
|
|
return None
|
|
current_id = target_data["info"].get("color_strip_source_id", "")
|
|
sources = self.coordinator.data.get("css_sources") or []
|
|
for s in sources:
|
|
if s["id"] == current_id:
|
|
return s["name"]
|
|
return None
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
if not self.coordinator.data:
|
|
return False
|
|
return self._target_id in self.coordinator.data.get("targets", {})
|
|
|
|
async def async_select_option(self, option: str) -> None:
|
|
source_id = self._name_to_id(option)
|
|
if source_id is None:
|
|
_LOGGER.error("CSS source not found: %s", option)
|
|
return
|
|
await self.coordinator.update_target(
|
|
self._target_id, color_strip_source_id=source_id
|
|
)
|
|
|
|
def _name_to_id(self, name: str) -> str | None:
|
|
sources = (self.coordinator.data or {}).get("css_sources") or []
|
|
for s in sources:
|
|
if s["name"] == name:
|
|
return s["id"]
|
|
return None
|
|
|
|
|
|
class BrightnessSourceSelect(CoordinatorEntity, SelectEntity):
|
|
"""Select entity for choosing a brightness value source for an LED target."""
|
|
|
|
_attr_has_entity_name = True
|
|
_attr_icon = "mdi:brightness-auto"
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: WLEDScreenControllerCoordinator,
|
|
target_id: str,
|
|
entry_id: str,
|
|
) -> None:
|
|
super().__init__(coordinator)
|
|
self._target_id = target_id
|
|
self._entry_id = entry_id
|
|
self._attr_unique_id = f"{target_id}_brightness_source"
|
|
self._attr_translation_key = "brightness_source"
|
|
|
|
@property
|
|
def device_info(self) -> dict[str, Any]:
|
|
return {"identifiers": {(DOMAIN, self._target_id)}}
|
|
|
|
@property
|
|
def options(self) -> list[str]:
|
|
if not self.coordinator.data:
|
|
return [NONE_OPTION]
|
|
sources = self.coordinator.data.get("value_sources") or []
|
|
return [NONE_OPTION] + [s["name"] for s in sources]
|
|
|
|
@property
|
|
def current_option(self) -> str | None:
|
|
if not self.coordinator.data:
|
|
return None
|
|
target_data = self.coordinator.data.get("targets", {}).get(self._target_id)
|
|
if not target_data:
|
|
return None
|
|
current_id = target_data["info"].get("brightness_value_source_id", "")
|
|
if not current_id:
|
|
return NONE_OPTION
|
|
sources = self.coordinator.data.get("value_sources") or []
|
|
for s in sources:
|
|
if s["id"] == current_id:
|
|
return s["name"]
|
|
return NONE_OPTION
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
if not self.coordinator.data:
|
|
return False
|
|
return self._target_id in self.coordinator.data.get("targets", {})
|
|
|
|
async def async_select_option(self, option: str) -> None:
|
|
if option == NONE_OPTION:
|
|
source_id = ""
|
|
else:
|
|
source_id = self._name_to_id(option)
|
|
if source_id is None:
|
|
_LOGGER.error("Value source not found: %s", option)
|
|
return
|
|
await self.coordinator.update_target(
|
|
self._target_id, brightness_value_source_id=source_id
|
|
)
|
|
|
|
def _name_to_id(self, name: str) -> str | None:
|
|
sources = (self.coordinator.data or {}).get("value_sources") or []
|
|
for s in sources:
|
|
if s["name"] == name:
|
|
return s["id"]
|
|
return None
|