feat(api-input): expose timeout/interpolation, fix 422 on light turn-on

- coordinator.update_source now auto-injects source_type from cached data so
  the server's discriminated-union body validates (was rejecting
  fallback_color updates with 422).
- Add Fallback Timeout (number) and Interpolation (select) entities per
  api_input CSS source.
- Light no longer zeros fallback_color on turn_off; state restored across
  HA restarts via RestoreEntity so the chosen default color persists.
- Bump manifest to 0.2.2 and update en/ru translations.
This commit is contained in:
2026-04-26 02:26:06 +03:00
parent 5c8b3c259a
commit 0a16dc9058
8 changed files with 226 additions and 14 deletions
+43 -12
View File
@@ -13,6 +13,7 @@ from homeassistant.components.light import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, DATA_COORDINATOR
@@ -41,8 +42,16 @@ async def async_setup_entry(
async_add_entities(entities)
class ApiInputLight(CoordinatorEntity, LightEntity):
"""Representation of an api_input CSS source as a light entity."""
class ApiInputLight(CoordinatorEntity, LightEntity, RestoreEntity):
"""Representation of an api_input CSS source as a light entity.
The light's RGB color doubles as the source's ``fallback_color`` (the
resting color shown when no API input arrives within ``timeout``).
Turning the light off only pushes black segments — it does *not* zero
``fallback_color`` — so the chosen default color persists across off/on
cycles. The on/off state itself is restored across HA restarts via
``RestoreEntity``.
"""
_attr_has_entity_name = True
_attr_color_mode = ColorMode.RGB
@@ -63,15 +72,37 @@ class ApiInputLight(CoordinatorEntity, LightEntity):
self._entry_id = entry_id
self._attr_unique_id = f"{self._source_id}_light"
# Restore state from fallback_color
# Seed color from the source's current fallback_color; default to
# white if it's unset/black so the picker has a sensible starting
# value. is_on is provisional here and refined in async_added_to_hass
# from RestoreEntity, since fallback_color alone no longer indicates
# on/off.
fallback = self._get_fallback_color()
is_off = fallback == [0, 0, 0]
self._is_on: bool = not is_off
has_color = fallback != [0, 0, 0]
self._rgb_color: tuple[int, int, int] = (
(255, 255, 255) if is_off else tuple(fallback) # type: ignore[arg-type]
tuple(fallback) if has_color else (255, 255, 255) # type: ignore[arg-type]
)
self._is_on: bool = has_color
self._brightness: int = 255
async def async_added_to_hass(self) -> None:
"""Restore on/off state and last color from the previous HA session."""
await super().async_added_to_hass()
last_state = await self.async_get_last_state()
if last_state is None:
return
self._is_on = last_state.state == "on"
last_rgb = last_state.attributes.get("rgb_color")
if (
isinstance(last_rgb, (list, tuple))
and len(last_rgb) == 3
and all(isinstance(c, (int, float)) for c in last_rgb)
):
self._rgb_color = tuple(int(c) for c in last_rgb) # type: ignore[assignment]
last_brightness = last_state.attributes.get("brightness")
if isinstance(last_brightness, (int, float)):
self._brightness = int(last_brightness)
@property
def device_info(self) -> dict[str, Any]:
"""Return device information — one virtual device per api_input source."""
@@ -126,14 +157,14 @@ class ApiInputLight(CoordinatorEntity, LightEntity):
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]
"""Turn off the light by pushing black segments.
``fallback_color`` is intentionally left untouched so the chosen
default color survives off/on cycles.
"""
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,
[{"start": 0, "length": 9999, "mode": "solid", "color": [0, 0, 0]}],
)
self._is_on = False
self.async_write_ha_state()