2021-10-22 12:30:03 +00:00
|
|
|
"""Support for AVM FRITZ!SmartHome lightbulbs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
from homeassistant.components.light import (
|
|
|
|
ATTR_BRIGHTNESS,
|
|
|
|
ATTR_COLOR_TEMP,
|
|
|
|
ATTR_HS_COLOR,
|
|
|
|
COLOR_MODE_COLOR_TEMP,
|
|
|
|
COLOR_MODE_HS,
|
|
|
|
LightEntity,
|
|
|
|
)
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
|
|
from homeassistant.util import color
|
|
|
|
|
|
|
|
from . import FritzBoxEntity
|
|
|
|
from .const import (
|
|
|
|
COLOR_MODE,
|
|
|
|
COLOR_TEMP_MODE,
|
|
|
|
CONF_COORDINATOR,
|
|
|
|
DOMAIN as FRITZBOX_DOMAIN,
|
|
|
|
)
|
2021-10-31 19:12:25 +00:00
|
|
|
from .coordinator import FritzboxDataUpdateCoordinator
|
2021-10-22 12:30:03 +00:00
|
|
|
|
|
|
|
SUPPORTED_COLOR_MODES = {COLOR_MODE_COLOR_TEMP, COLOR_MODE_HS}
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(
|
|
|
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
|
|
) -> None:
|
|
|
|
"""Set up the FRITZ!SmartHome light from ConfigEntry."""
|
|
|
|
entities: list[FritzboxLight] = []
|
|
|
|
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
|
|
|
|
|
|
|
for ain, device in coordinator.data.items():
|
|
|
|
if not device.has_lightbulb:
|
|
|
|
continue
|
|
|
|
|
|
|
|
supported_color_temps = await hass.async_add_executor_job(
|
|
|
|
device.get_color_temps
|
|
|
|
)
|
|
|
|
|
|
|
|
supported_colors = await hass.async_add_executor_job(device.get_colors)
|
|
|
|
|
|
|
|
entities.append(
|
|
|
|
FritzboxLight(
|
|
|
|
coordinator,
|
|
|
|
ain,
|
|
|
|
supported_colors,
|
|
|
|
supported_color_temps,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
async_add_entities(entities)
|
|
|
|
|
|
|
|
|
|
|
|
class FritzboxLight(FritzBoxEntity, LightEntity):
|
|
|
|
"""The light class for FRITZ!SmartHome lightbulbs."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
2021-10-31 19:12:25 +00:00
|
|
|
coordinator: FritzboxDataUpdateCoordinator,
|
2021-10-22 12:30:03 +00:00
|
|
|
ain: str,
|
|
|
|
supported_colors: dict,
|
|
|
|
supported_color_temps: list[str],
|
|
|
|
) -> None:
|
|
|
|
"""Initialize the FritzboxLight entity."""
|
|
|
|
super().__init__(coordinator, ain, None)
|
|
|
|
|
|
|
|
max_kelvin = int(max(supported_color_temps))
|
|
|
|
min_kelvin = int(min(supported_color_temps))
|
|
|
|
|
|
|
|
# max kelvin is min mireds and min kelvin is max mireds
|
|
|
|
self._attr_min_mireds = color.color_temperature_kelvin_to_mired(max_kelvin)
|
|
|
|
self._attr_max_mireds = color.color_temperature_kelvin_to_mired(min_kelvin)
|
|
|
|
|
|
|
|
# Fritz!DECT 500 only supports 12 values for hue, with 3 saturations each.
|
|
|
|
# Map supported colors to dict {hue: [sat1, sat2, sat3]} for easier lookup
|
|
|
|
self._supported_hs = {}
|
|
|
|
for values in supported_colors.values():
|
|
|
|
hue = int(values[0][0])
|
|
|
|
self._supported_hs[hue] = [
|
|
|
|
int(values[0][1]),
|
|
|
|
int(values[1][1]),
|
|
|
|
int(values[2][1]),
|
|
|
|
]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self) -> bool:
|
|
|
|
"""If the light is currently on or off."""
|
|
|
|
return self.device.state # type: ignore [no-any-return]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def brightness(self) -> int:
|
|
|
|
"""Return the current Brightness."""
|
|
|
|
return self.device.level # type: ignore [no-any-return]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def hs_color(self) -> tuple[float, float] | None:
|
|
|
|
"""Return the hs color value."""
|
|
|
|
if self.device.color_mode != COLOR_MODE:
|
|
|
|
return None
|
|
|
|
|
|
|
|
hue = self.device.hue
|
|
|
|
saturation = self.device.saturation
|
|
|
|
|
|
|
|
return (hue, float(saturation) * 100.0 / 255.0)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def color_temp(self) -> int | None:
|
|
|
|
"""Return the CT color value."""
|
|
|
|
if self.device.color_mode != COLOR_TEMP_MODE:
|
|
|
|
return None
|
|
|
|
|
|
|
|
kelvin = self.device.color_temp
|
|
|
|
return color.color_temperature_kelvin_to_mired(kelvin)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_color_modes(self) -> set:
|
|
|
|
"""Flag supported color modes."""
|
|
|
|
return SUPPORTED_COLOR_MODES
|
|
|
|
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the light on."""
|
|
|
|
if kwargs.get(ATTR_BRIGHTNESS) is not None:
|
|
|
|
level = kwargs[ATTR_BRIGHTNESS]
|
|
|
|
await self.hass.async_add_executor_job(self.device.set_level, level)
|
|
|
|
if kwargs.get(ATTR_HS_COLOR) is not None:
|
|
|
|
hass_hue = int(kwargs[ATTR_HS_COLOR][0])
|
|
|
|
hass_saturation = round(kwargs[ATTR_HS_COLOR][1] * 255.0 / 100.0)
|
|
|
|
# find supported hs values closest to what user selected
|
|
|
|
hue = min(self._supported_hs.keys(), key=lambda x: abs(x - hass_hue))
|
|
|
|
saturation = min(
|
|
|
|
self._supported_hs[hue], key=lambda x: abs(x - hass_saturation)
|
|
|
|
)
|
|
|
|
await self.hass.async_add_executor_job(
|
|
|
|
self.device.set_color, (hue, saturation)
|
|
|
|
)
|
|
|
|
|
|
|
|
if kwargs.get(ATTR_COLOR_TEMP) is not None:
|
|
|
|
kelvin = color.color_temperature_kelvin_to_mired(kwargs[ATTR_COLOR_TEMP])
|
|
|
|
await self.hass.async_add_executor_job(self.device.set_color_temp, kelvin)
|
|
|
|
|
|
|
|
await self.hass.async_add_executor_job(self.device.set_state_on)
|
|
|
|
await self.coordinator.async_refresh()
|
|
|
|
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the light off."""
|
|
|
|
await self.hass.async_add_executor_job(self.device.set_state_off)
|
|
|
|
await self.coordinator.async_refresh()
|