228 lines
8.1 KiB
Python
228 lines
8.1 KiB
Python
"""Support for IKEA Tradfri lights."""
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import Any, cast
|
|
|
|
from pytradfri.command import Command
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_COLOR_TEMP,
|
|
ATTR_HS_COLOR,
|
|
ATTR_TRANSITION,
|
|
ColorMode,
|
|
LightEntity,
|
|
LightEntityFeature,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
import homeassistant.util.color as color_util
|
|
|
|
from .base_class import TradfriBaseEntity
|
|
from .const import CONF_GATEWAY_ID, COORDINATOR, COORDINATOR_LIST, DOMAIN, KEY_API
|
|
from .coordinator import TradfriDeviceDataUpdateCoordinator
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: ConfigEntry,
|
|
async_add_entities: AddEntitiesCallback,
|
|
) -> None:
|
|
"""Load Tradfri lights based on a config entry."""
|
|
gateway_id = config_entry.data[CONF_GATEWAY_ID]
|
|
coordinator_data = hass.data[DOMAIN][config_entry.entry_id][COORDINATOR]
|
|
api = coordinator_data[KEY_API]
|
|
|
|
async_add_entities(
|
|
TradfriLight(
|
|
device_coordinator,
|
|
api,
|
|
gateway_id,
|
|
)
|
|
for device_coordinator in coordinator_data[COORDINATOR_LIST]
|
|
if device_coordinator.device.has_light_control
|
|
)
|
|
|
|
|
|
class TradfriLight(TradfriBaseEntity, LightEntity):
|
|
"""The platform class required by Home Assistant."""
|
|
|
|
_attr_name = None
|
|
_attr_supported_features = LightEntityFeature.TRANSITION
|
|
|
|
def __init__(
|
|
self,
|
|
device_coordinator: TradfriDeviceDataUpdateCoordinator,
|
|
api: Callable[[Command | list[Command]], Any],
|
|
gateway_id: str,
|
|
) -> None:
|
|
"""Initialize a Light."""
|
|
super().__init__(
|
|
device_coordinator=device_coordinator,
|
|
api=api,
|
|
gateway_id=gateway_id,
|
|
)
|
|
|
|
self._device_control = self._device.light_control
|
|
self._device_data = self._device_control.lights[0]
|
|
|
|
self._attr_unique_id = f"light-{gateway_id}-{self._device_id}"
|
|
self._hs_color = None
|
|
|
|
# Calculate supported color modes
|
|
self._attr_supported_color_modes: set[ColorMode] = set()
|
|
if self._device.light_control.can_set_color:
|
|
self._attr_supported_color_modes.add(ColorMode.HS)
|
|
if self._device.light_control.can_set_temp:
|
|
self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)
|
|
if (
|
|
not self._attr_supported_color_modes
|
|
and self._device.light_control.can_set_dimmer
|
|
):
|
|
# Must be the only supported mode according to docs for
|
|
# ColorMode.BRIGHTNESS
|
|
self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)
|
|
|
|
if self._device_control:
|
|
self._attr_min_mireds = self._device_control.min_mireds
|
|
self._attr_max_mireds = self._device_control.max_mireds
|
|
|
|
def _refresh(self) -> None:
|
|
"""Refresh the device."""
|
|
self._device_data = self.coordinator.data.light_control.lights[0]
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
"""Return true if light is on."""
|
|
if not self._device_data:
|
|
return False
|
|
return cast(bool, self._device_data.state)
|
|
|
|
@property
|
|
def brightness(self) -> int | None:
|
|
"""Return the brightness of the light."""
|
|
if not self._device_data:
|
|
return None
|
|
return cast(int, self._device_data.dimmer)
|
|
|
|
@property
|
|
def color_temp(self) -> int | None:
|
|
"""Return the color temp value in mireds."""
|
|
if not self._device_data:
|
|
return None
|
|
return cast(int, self._device_data.color_temp)
|
|
|
|
@property
|
|
def hs_color(self) -> tuple[float, float] | None:
|
|
"""HS color of the light."""
|
|
if not self._device_control or not self._device_data:
|
|
return None
|
|
if self._device_control.can_set_color:
|
|
hsbxy = self._device_data.hsb_xy_color
|
|
hue = hsbxy[0] / (self._device_control.max_hue / 360)
|
|
sat = hsbxy[1] / (self._device_control.max_saturation / 100)
|
|
if hue is not None and sat is not None:
|
|
return hue, sat
|
|
return None
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Instruct the light to turn off."""
|
|
# This allows transitioning to off, but resets the brightness
|
|
# to 1 for the next set_state(True) command
|
|
if not self._device_control:
|
|
return
|
|
transition_time = None
|
|
if ATTR_TRANSITION in kwargs:
|
|
transition_time = int(kwargs[ATTR_TRANSITION]) * 10
|
|
|
|
await self._api(
|
|
self._device_control.set_dimmer(
|
|
dimmer=0, transition_time=transition_time
|
|
)
|
|
)
|
|
else:
|
|
await self._api(self._device_control.set_state(False))
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Instruct the light to turn on."""
|
|
if not self._device_control:
|
|
return
|
|
transition_time = None
|
|
if ATTR_TRANSITION in kwargs:
|
|
transition_time = int(kwargs[ATTR_TRANSITION]) * 10
|
|
|
|
dimmer_command = None
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
brightness = kwargs[ATTR_BRIGHTNESS]
|
|
brightness = min(brightness, 254)
|
|
dimmer_data = {
|
|
"dimmer": brightness,
|
|
"transition_time": transition_time,
|
|
}
|
|
dimmer_command = self._device_control.set_dimmer(**dimmer_data)
|
|
transition_time = None
|
|
else:
|
|
dimmer_command = self._device_control.set_state(True)
|
|
|
|
color_command = None
|
|
if ATTR_HS_COLOR in kwargs and self._device_control.can_set_color:
|
|
hue = int(kwargs[ATTR_HS_COLOR][0] * (self._device_control.max_hue / 360))
|
|
sat = int(
|
|
kwargs[ATTR_HS_COLOR][1] * (self._device_control.max_saturation / 100)
|
|
)
|
|
color_data = {
|
|
"hue": hue,
|
|
"saturation": sat,
|
|
"transition_time": transition_time,
|
|
}
|
|
color_command = self._device_control.set_hsb(**color_data)
|
|
transition_time = None
|
|
|
|
temp_command = None
|
|
if ATTR_COLOR_TEMP in kwargs and (
|
|
self._device_control.can_set_temp or self._device_control.can_set_color
|
|
):
|
|
temp = kwargs[ATTR_COLOR_TEMP]
|
|
# White Spectrum bulb
|
|
if self._device_control.can_set_temp:
|
|
if temp > self.max_mireds:
|
|
temp = self.max_mireds
|
|
elif temp < self.min_mireds:
|
|
temp = self.min_mireds
|
|
temp_data = {
|
|
ATTR_COLOR_TEMP: temp,
|
|
"transition_time": transition_time,
|
|
}
|
|
temp_command = self._device_control.set_color_temp(**temp_data)
|
|
transition_time = None
|
|
# Color bulb (CWS)
|
|
# color_temp needs to be set with hue/saturation
|
|
elif self._device_control.can_set_color:
|
|
temp_k = color_util.color_temperature_mired_to_kelvin(temp)
|
|
hs_color = color_util.color_temperature_to_hs(temp_k)
|
|
hue = int(hs_color[0] * (self._device_control.max_hue / 360))
|
|
sat = int(hs_color[1] * (self._device_control.max_saturation / 100))
|
|
color_data = {
|
|
"hue": hue,
|
|
"saturation": sat,
|
|
"transition_time": transition_time,
|
|
}
|
|
color_command = self._device_control.set_hsb(**color_data)
|
|
transition_time = None
|
|
|
|
# HSB can always be set, but color temp + brightness is bulb dependent
|
|
if (command := dimmer_command) is not None:
|
|
command += color_command
|
|
else:
|
|
command = color_command
|
|
|
|
if self._device_control.can_combine_commands:
|
|
await self._api(command + temp_command)
|
|
else:
|
|
if temp_command is not None:
|
|
await self._api(temp_command)
|
|
if command is not None:
|
|
await self._api(command)
|