Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for AVM FRITZ!SmartHome lightbulbs."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from requests.exceptions import HTTPError
8 
10  ATTR_BRIGHTNESS,
11  ATTR_COLOR_TEMP_KELVIN,
12  ATTR_HS_COLOR,
13  ColorMode,
14  LightEntity,
15 )
16 from homeassistant.core import HomeAssistant, callback
17 from homeassistant.helpers.entity_platform import AddEntitiesCallback
18 
19 from .const import COLOR_MODE, LOGGER
20 from .coordinator import FritzboxConfigEntry, FritzboxDataUpdateCoordinator
21 from .entity import FritzBoxDeviceEntity
22 
23 
25  hass: HomeAssistant,
26  entry: FritzboxConfigEntry,
27  async_add_entities: AddEntitiesCallback,
28 ) -> None:
29  """Set up the FRITZ!SmartHome light from ConfigEntry."""
30  coordinator = entry.runtime_data
31 
32  @callback
33  def _add_entities(devices: set[str] | None = None) -> None:
34  """Add devices."""
35  if devices is None:
36  devices = coordinator.new_devices
37  if not devices:
38  return
40  FritzboxLight(coordinator, ain)
41  for ain in devices
42  if coordinator.data.devices[ain].has_lightbulb
43  )
44 
45  entry.async_on_unload(coordinator.async_add_listener(_add_entities))
46 
47  _add_entities(set(coordinator.data.devices))
48 
49 
51  """The light class for FRITZ!SmartHome lightbulbs."""
52 
53  def __init__(
54  self,
55  coordinator: FritzboxDataUpdateCoordinator,
56  ain: str,
57  ) -> None:
58  """Initialize the FritzboxLight entity."""
59  super().__init__(coordinator, ain, None)
60  self._supported_hs: dict[int, list[int]] = {}
61 
62  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.ONOFF}
63  if self.datadatadatadatadata.has_color:
64  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
65  elif self.datadatadatadatadata.has_level:
66  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
67 
68  @property
69  def is_on(self) -> bool:
70  """If the light is currently on or off."""
71  return self.datadatadatadatadata.state # type: ignore [no-any-return]
72 
73  @property
74  def brightness(self) -> int:
75  """Return the current Brightness."""
76  return self.datadatadatadatadata.level # type: ignore [no-any-return]
77 
78  @property
79  def hs_color(self) -> tuple[float, float]:
80  """Return the hs color value."""
81  hue = self.datadatadatadatadata.hue
82  saturation = self.datadatadatadatadata.saturation
83 
84  return (hue, float(saturation) * 100.0 / 255.0)
85 
86  @property
87  def color_temp_kelvin(self) -> int:
88  """Return the CT color value."""
89  return self.datadatadatadatadata.color_temp # type: ignore [no-any-return]
90 
91  @property
92  def color_mode(self) -> ColorMode:
93  """Return the color mode of the light."""
94  if self.datadatadatadatadata.has_color:
95  if self.datadatadatadatadata.color_mode == COLOR_MODE:
96  return ColorMode.HS
97  return ColorMode.COLOR_TEMP
98  if self.datadatadatadatadata.has_level:
99  return ColorMode.BRIGHTNESS
100  return ColorMode.ONOFF
101 
102  async def async_turn_on(self, **kwargs: Any) -> None:
103  """Turn the light on."""
104  if kwargs.get(ATTR_BRIGHTNESS) is not None:
105  level = kwargs[ATTR_BRIGHTNESS]
106  await self.hasshasshass.async_add_executor_job(self.datadatadatadatadata.set_level, level)
107  if kwargs.get(ATTR_HS_COLOR) is not None:
108  # Try setunmappedcolor first. This allows free color selection,
109  # but we don't know if its supported by all devices.
110  try:
111  # HA gives 0..360 for hue, fritz light only supports 0..359
112  unmapped_hue = int(kwargs[ATTR_HS_COLOR][0] % 360)
113  unmapped_saturation = round(
114  cast(float, kwargs[ATTR_HS_COLOR][1]) * 255.0 / 100.0
115  )
116  await self.hasshasshass.async_add_executor_job(
117  self.datadatadatadatadata.set_unmapped_color, (unmapped_hue, unmapped_saturation)
118  )
119  # This will raise 400 BAD REQUEST if the setunmappedcolor is not available
120  except HTTPError as err:
121  if err.response.status_code != 400:
122  raise
123  LOGGER.debug(
124  "fritzbox does not support method 'setunmappedcolor', fallback to"
125  " 'setcolor'"
126  )
127  # find supported hs values closest to what user selected
128  hue = min(
129  self._supported_hs.keys(), key=lambda x: abs(x - unmapped_hue)
130  )
131  saturation = min(
132  self._supported_hs[hue],
133  key=lambda x: abs(x - unmapped_saturation),
134  )
135  await self.hasshasshass.async_add_executor_job(
136  self.datadatadatadatadata.set_color, (hue, saturation)
137  )
138 
139  if kwargs.get(ATTR_COLOR_TEMP_KELVIN) is not None:
140  await self.hasshasshass.async_add_executor_job(
141  self.datadatadatadatadata.set_color_temp, kwargs[ATTR_COLOR_TEMP_KELVIN]
142  )
143 
144  await self.hasshasshass.async_add_executor_job(self.datadatadatadatadata.set_state_on)
145  await self.coordinator.async_refresh()
146 
147  async def async_turn_off(self, **kwargs: Any) -> None:
148  """Turn the light off."""
149  await self.hasshasshass.async_add_executor_job(self.datadatadatadatadata.set_state_off)
150  await self.coordinator.async_refresh()
151 
152  async def async_added_to_hass(self) -> None:
153  """Get light attributes from device after entity is added to hass."""
154  await super().async_added_to_hass()
155 
156  def _get_color_data() -> tuple[dict, list]:
157  return (self.datadatadatadatadata.get_colors(), self.datadatadatadatadata.get_color_temps())
158 
159  (
160  supported_colors,
161  supported_color_temps,
162  ) = await self.hasshasshass.async_add_executor_job(_get_color_data)
163 
164  if supported_color_temps:
165  # only available for color bulbs
166  self._attr_max_color_temp_kelvin_attr_max_color_temp_kelvin = int(max(supported_color_temps))
167  self._attr_min_color_temp_kelvin_attr_min_color_temp_kelvin = int(min(supported_color_temps))
168 
169  # Fritz!DECT 500 only supports 12 values for hue, with 3 saturations each.
170  # Map supported colors to dict {hue: [sat1, sat2, sat3]} for easier lookup
171  for values in supported_colors.values():
172  hue = int(values[0][0])
173  self._supported_hs[hue] = [
174  int(values[0][1]),
175  int(values[1][1]),
176  int(values[2][1]),
177  ]
None __init__(self, FritzboxDataUpdateCoordinator coordinator, str ain)
Definition: light.py:57
None async_setup_entry(HomeAssistant hass, FritzboxConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: light.py:28