579553a850
HACS-compatible custom component split out from the main LedGrab repo. Creates light, switch, sensor, number, and select entities for each configured LedGrab device.
152 lines
5.0 KiB
Python
152 lines
5.0 KiB
Python
"""Light platform for LED Screen Controller (api_input CSS sources)."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_RGB_COLOR,
|
|
ColorMode,
|
|
LightEntity,
|
|
)
|
|
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
|
|
from .coordinator import LedGrabCoordinator
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Set up LED Screen Controller api_input lights."""
|
|
data = hass.data[DOMAIN][entry.entry_id]
|
|
coordinator: LedGrabCoordinator = data[DATA_COORDINATOR]
|
|
|
|
entities = []
|
|
if coordinator.data:
|
|
for source in coordinator.data.get("css_sources", []):
|
|
if source.get("source_type") == "api_input":
|
|
entities.append(
|
|
ApiInputLight(coordinator, source, entry.entry_id)
|
|
)
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
class ApiInputLight(CoordinatorEntity, LightEntity):
|
|
"""Representation of an api_input CSS source as a light entity."""
|
|
|
|
_attr_has_entity_name = True
|
|
_attr_color_mode = ColorMode.RGB
|
|
_attr_supported_color_modes = {ColorMode.RGB}
|
|
_attr_translation_key = "api_input_light"
|
|
_attr_icon = "mdi:led-strip-variant"
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: LedGrabCoordinator,
|
|
source: dict[str, Any],
|
|
entry_id: str,
|
|
) -> None:
|
|
"""Initialize the light."""
|
|
super().__init__(coordinator)
|
|
self._source_id: str = source["id"]
|
|
self._source_name: str = source.get("name", self._source_id)
|
|
self._entry_id = entry_id
|
|
self._attr_unique_id = f"{self._source_id}_light"
|
|
|
|
# Restore state from fallback_color
|
|
fallback = self._get_fallback_color()
|
|
is_off = fallback == [0, 0, 0]
|
|
self._is_on: bool = not is_off
|
|
self._rgb_color: tuple[int, int, int] = (
|
|
(255, 255, 255) if is_off else tuple(fallback) # type: ignore[arg-type]
|
|
)
|
|
self._brightness: int = 255
|
|
|
|
@property
|
|
def device_info(self) -> dict[str, Any]:
|
|
"""Return device information — one virtual device per api_input source."""
|
|
return {
|
|
"identifiers": {(DOMAIN, self._source_id)},
|
|
"name": self._source_name,
|
|
"manufacturer": "LedGrab",
|
|
"model": "API Input CSS Source",
|
|
}
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
"""Return the entity name."""
|
|
return self._source_name
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
"""Return true if the light is on."""
|
|
return self._is_on
|
|
|
|
@property
|
|
def rgb_color(self) -> tuple[int, int, int]:
|
|
"""Return the current RGB color."""
|
|
return self._rgb_color
|
|
|
|
@property
|
|
def brightness(self) -> int:
|
|
"""Return the current brightness (0-255)."""
|
|
return self._brightness
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn on the light, optionally setting color and brightness."""
|
|
if ATTR_RGB_COLOR in kwargs:
|
|
self._rgb_color = kwargs[ATTR_RGB_COLOR]
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
|
|
|
# Scale RGB by brightness
|
|
scale = self._brightness / 255
|
|
r, g, b = self._rgb_color
|
|
scaled = [round(r * scale), round(g * scale), round(b * scale)]
|
|
|
|
await self.coordinator.push_segments(
|
|
self._source_id,
|
|
[{"start": 0, "length": 9999, "mode": "solid", "color": scaled}],
|
|
)
|
|
# Update fallback_color so the color persists beyond the timeout
|
|
await self.coordinator.update_source(
|
|
self._source_id, fallback_color=scaled,
|
|
)
|
|
self._is_on = True
|
|
self.async_write_ha_state()
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn off the light by pushing black and setting fallback to black."""
|
|
off_color = [0, 0, 0]
|
|
await self.coordinator.push_segments(
|
|
self._source_id,
|
|
[{"start": 0, "length": 9999, "mode": "solid", "color": off_color}],
|
|
)
|
|
await self.coordinator.update_source(
|
|
self._source_id, fallback_color=off_color,
|
|
)
|
|
self._is_on = False
|
|
self.async_write_ha_state()
|
|
|
|
def _get_fallback_color(self) -> list[int]:
|
|
"""Read fallback_color from the source config in coordinator data."""
|
|
if not self.coordinator.data:
|
|
return [0, 0, 0]
|
|
for source in self.coordinator.data.get("css_sources", []):
|
|
if source.get("id") == self._source_id:
|
|
fallback = source.get("fallback_color")
|
|
if fallback and len(fallback) >= 3:
|
|
return list(fallback[:3])
|
|
break
|
|
return [0, 0, 0]
|