127 lines
4.0 KiB
Python
127 lines
4.0 KiB
Python
"""Light platform for ninlight lamps."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_COLOR_TEMP_KELVIN,
|
|
ATTR_RGB_COLOR,
|
|
ATTR_TRANSITION,
|
|
ColorMode,
|
|
LightEntity,
|
|
LightEntityFeature,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from .const import MAX_TRANSITION_MS
|
|
from .coordinator import NinlightConfigEntry, NinlightCoordinator
|
|
from .entity import NinlightEntity
|
|
|
|
PARALLEL_UPDATES = 0 # the coordinator lock serializes per lamp
|
|
|
|
_FALLBACK_KELVIN = (2700, 6500)
|
|
|
|
|
|
def ha_to_lamp_brightness(value: int) -> int:
|
|
"""HA 1-255 -> lamp 1-100. Never 0: 0 would switch the lamp off."""
|
|
return max(1, min(100, round(value * 100 / 255)))
|
|
|
|
|
|
def lamp_to_ha_brightness(value: int) -> int:
|
|
"""Lamp 1-100 -> HA 1-255; round-trips back to the same lamp value."""
|
|
return max(1, min(255, round(value * 255 / 100)))
|
|
|
|
|
|
def transition_ms(seconds: float) -> int:
|
|
return max(0, min(MAX_TRANSITION_MS, round(seconds * 1000)))
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: NinlightConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
async_add_entities([NinlightLight(entry.runtime_data)])
|
|
|
|
|
|
class NinlightLight(NinlightEntity, LightEntity):
|
|
"""The lamp itself."""
|
|
|
|
_attr_name = None
|
|
_attr_supported_features = LightEntityFeature.TRANSITION
|
|
|
|
def __init__(self, coordinator: NinlightCoordinator) -> None:
|
|
super().__init__(coordinator, "light")
|
|
|
|
@property
|
|
def _state(self) -> dict[str, Any]:
|
|
return self.coordinator.data.state
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
return bool(self._state["on"])
|
|
|
|
@property
|
|
def brightness(self) -> int:
|
|
return lamp_to_ha_brightness(int(self._state["brightness"]))
|
|
|
|
@property
|
|
def supported_color_modes(self) -> set[ColorMode]:
|
|
if self._state["capabilities"].get("color"):
|
|
return {ColorMode.RGB, ColorMode.COLOR_TEMP}
|
|
return {ColorMode.COLOR_TEMP}
|
|
|
|
@property
|
|
def color_mode(self) -> ColorMode:
|
|
if self._state.get("mode") == "color" and ColorMode.RGB in self.supported_color_modes:
|
|
return ColorMode.RGB
|
|
return ColorMode.COLOR_TEMP
|
|
|
|
@property
|
|
def rgb_color(self) -> tuple[int, int, int] | None:
|
|
color = self._state.get("color")
|
|
if not isinstance(color, dict):
|
|
return None
|
|
return (int(color["r"]), int(color["g"]), int(color["b"]))
|
|
|
|
@property
|
|
def color_temp_kelvin(self) -> int | None:
|
|
value = self._state.get("color_temp")
|
|
return int(value) if value is not None else None
|
|
|
|
def _kelvin_range(self) -> tuple[int, int]:
|
|
value = self._state["capabilities"].get("color_temp")
|
|
if isinstance(value, list) and len(value) == 2:
|
|
return (int(value[0]), int(value[1]))
|
|
return _FALLBACK_KELVIN
|
|
|
|
@property
|
|
def min_color_temp_kelvin(self) -> int:
|
|
return self._kelvin_range()[0]
|
|
|
|
@property
|
|
def max_color_temp_kelvin(self) -> int:
|
|
return self._kelvin_range()[1]
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
fields: dict[str, Any] = {"on": True}
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
fields["brightness"] = ha_to_lamp_brightness(kwargs[ATTR_BRIGHTNESS])
|
|
if ATTR_RGB_COLOR in kwargs:
|
|
red, green, blue = kwargs[ATTR_RGB_COLOR]
|
|
fields["color"] = {"r": red, "g": green, "b": blue}
|
|
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
|
fields["color_temp"] = int(kwargs[ATTR_COLOR_TEMP_KELVIN])
|
|
if ATTR_TRANSITION in kwargs:
|
|
fields["transition"] = transition_ms(kwargs[ATTR_TRANSITION])
|
|
await self.coordinator.async_set_state(**fields)
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
fields: dict[str, Any] = {"on": False}
|
|
if ATTR_TRANSITION in kwargs:
|
|
fields["transition"] = transition_ms(kwargs[ATTR_TRANSITION])
|
|
await self.coordinator.async_set_state(**fields)
|