Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for LED lights."""
2 
3 from __future__ import annotations
4 
5 from functools import partial
6 from typing import Any, cast
7 
9  ATTR_BRIGHTNESS,
10  ATTR_COLOR_TEMP_KELVIN,
11  ATTR_EFFECT,
12  ATTR_RGB_COLOR,
13  ATTR_RGBW_COLOR,
14  ATTR_TRANSITION,
15  ColorMode,
16  LightEntity,
17  LightEntityFeature,
18 )
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 
22 from . import WLEDConfigEntry
23 from .const import (
24  ATTR_CCT,
25  ATTR_COLOR_PRIMARY,
26  ATTR_ON,
27  ATTR_SEGMENT_ID,
28  COLOR_TEMP_K_MAX,
29  COLOR_TEMP_K_MIN,
30  LIGHT_CAPABILITIES_COLOR_MODE_MAPPING,
31 )
32 from .coordinator import WLEDDataUpdateCoordinator
33 from .entity import WLEDEntity
34 from .helpers import kelvin_to_255, kelvin_to_255_reverse, wled_exception_handler
35 
36 PARALLEL_UPDATES = 1
37 
38 
40  hass: HomeAssistant,
41  entry: WLEDConfigEntry,
42  async_add_entities: AddEntitiesCallback,
43 ) -> None:
44  """Set up WLED light based on a config entry."""
45  coordinator = entry.runtime_data
46  if coordinator.keep_main_light:
47  async_add_entities([WLEDMainLight(coordinator=coordinator)])
48 
49  update_segments = partial(
50  async_update_segments,
51  coordinator,
52  set(),
53  async_add_entities,
54  )
55 
56  coordinator.async_add_listener(update_segments)
57  update_segments()
58 
59 
61  """Defines a WLED main light."""
62 
63  _attr_color_mode = ColorMode.BRIGHTNESS
64  _attr_translation_key = "main"
65  _attr_supported_features = LightEntityFeature.TRANSITION
66  _attr_supported_color_modes = {ColorMode.BRIGHTNESS}
67 
68  def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None:
69  """Initialize WLED main light."""
70  super().__init__(coordinator=coordinator)
71  self._attr_unique_id_attr_unique_id = coordinator.data.info.mac_address
72 
73  @property
74  def brightness(self) -> int | None:
75  """Return the brightness of this light between 1..255."""
76  return self.coordinator.data.state.brightness
77 
78  @property
79  def is_on(self) -> bool:
80  """Return the state of the light."""
81  return bool(self.coordinator.data.state.on)
82 
83  @property
84  def available(self) -> bool:
85  """Return if this main light is available or not."""
86  return self.coordinator.has_main_light and super().available
87 
88  @wled_exception_handler
89  async def async_turn_off(self, **kwargs: Any) -> None:
90  """Turn off the light."""
91  transition = None
92  if ATTR_TRANSITION in kwargs:
93  # WLED uses 100ms per unit, so 10 = 1 second.
94  transition = round(kwargs[ATTR_TRANSITION] * 10)
95 
96  await self.coordinator.wled.master(on=False, transition=transition)
97 
98  @wled_exception_handler
99  async def async_turn_on(self, **kwargs: Any) -> None:
100  """Turn on the light."""
101  transition = None
102  if ATTR_TRANSITION in kwargs:
103  # WLED uses 100ms per unit, so 10 = 1 second.
104  transition = round(kwargs[ATTR_TRANSITION] * 10)
105 
106  await self.coordinator.wled.master(
107  on=True, brightness=kwargs.get(ATTR_BRIGHTNESS), transition=transition
108  )
109 
110 
112  """Defines a WLED light based on a segment."""
113 
114  _attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION
115  _attr_translation_key = "segment"
116  _attr_min_color_temp_kelvin = COLOR_TEMP_K_MIN
117  _attr_max_color_temp_kelvin = COLOR_TEMP_K_MAX
118 
119  def __init__(
120  self,
121  coordinator: WLEDDataUpdateCoordinator,
122  segment: int,
123  ) -> None:
124  """Initialize WLED segment light."""
125  super().__init__(coordinator=coordinator)
126  self._segment_segment = segment
127 
128  # Segment 0 uses a simpler name, which is more natural for when using
129  # a single segment / using WLED with one big LED strip.
130  if segment == 0:
131  self._attr_name_attr_name = None
132  else:
133  self._attr_translation_placeholders_attr_translation_placeholders = {"segment": str(segment)}
134 
135  self._attr_unique_id_attr_unique_id = (
136  f"{self.coordinator.data.info.mac_address}_{self._segment}"
137  )
138 
139  if (
140  coordinator.data.info.leds.segment_light_capabilities is not None
141  and (
142  color_modes := LIGHT_CAPABILITIES_COLOR_MODE_MAPPING.get(
143  coordinator.data.info.leds.segment_light_capabilities[segment]
144  )
145  )
146  is not None
147  ):
148  self._attr_color_mode_attr_color_mode = color_modes[0]
149  self._attr_supported_color_modes_attr_supported_color_modes = set(color_modes)
150 
151  @property
152  def available(self) -> bool:
153  """Return True if entity is available."""
154  try:
155  self.coordinator.data.state.segments[self._segment_segment]
156  except KeyError:
157  return False
158 
159  return super().available
160 
161  @property
162  def rgb_color(self) -> tuple[int, int, int] | None:
163  """Return the color value."""
164  if not (color := self.coordinator.data.state.segments[self._segment_segment].color):
165  return None
166  return color.primary[:3]
167 
168  @property
169  def rgbw_color(self) -> tuple[int, int, int, int] | None:
170  """Return the color value."""
171  if not (color := self.coordinator.data.state.segments[self._segment_segment].color):
172  return None
173  return cast(tuple[int, int, int, int], color.primary)
174 
175  @property
176  def color_temp_kelvin(self) -> int | None:
177  """Return the CT color value in K."""
178  cct = self.coordinator.data.state.segments[self._segment_segment].cct
179  return kelvin_to_255_reverse(cct, COLOR_TEMP_K_MIN, COLOR_TEMP_K_MAX)
180 
181  @property
182  def effect(self) -> str | None:
183  """Return the current effect of the light."""
184  return self.coordinator.data.effects[
185  int(self.coordinator.data.state.segments[self._segment_segment].effect_id)
186  ].name
187 
188  @property
189  def brightness(self) -> int | None:
190  """Return the brightness of this light between 1..255."""
191  state = self.coordinator.data.state
192 
193  # If this is the one and only segment, calculate brightness based
194  # on the main and segment brightness
195  if not self.coordinator.has_main_light:
196  return int(
197  (state.segments[self._segment_segment].brightness * state.brightness) / 255
198  )
199 
200  return state.segments[self._segment_segment].brightness
201 
202  @property
203  def effect_list(self) -> list[str]:
204  """Return the list of supported effects."""
205  return [effect.name for effect in self.coordinator.data.effects.values()]
206 
207  @property
208  def is_on(self) -> bool:
209  """Return the state of the light."""
210  state = self.coordinator.data.state
211 
212  # If there is no main, we take the main state into account
213  # on the segment level.
214  if not self.coordinator.has_main_light and not state.on:
215  return False
216 
217  return bool(state.segments[self._segment_segment].on)
218 
219  @wled_exception_handler
220  async def async_turn_off(self, **kwargs: Any) -> None:
221  """Turn off the light."""
222  transition = None
223  if ATTR_TRANSITION in kwargs:
224  # WLED uses 100ms per unit, so 10 = 1 second.
225  transition = round(kwargs[ATTR_TRANSITION] * 10)
226 
227  # If there is no main control, and only 1 segment, handle the main
228  if not self.coordinator.has_main_light:
229  await self.coordinator.wled.master(on=False, transition=transition)
230  return
231 
232  await self.coordinator.wled.segment(
233  segment_id=self._segment_segment, on=False, transition=transition
234  )
235 
236  @wled_exception_handler
237  async def async_turn_on(self, **kwargs: Any) -> None:
238  """Turn on the light."""
239  data: dict[str, Any] = {
240  ATTR_ON: True,
241  ATTR_SEGMENT_ID: self._segment_segment,
242  }
243 
244  if ATTR_RGB_COLOR in kwargs:
245  data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGB_COLOR]
246 
247  if ATTR_RGBW_COLOR in kwargs:
248  data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGBW_COLOR]
249 
250  if ATTR_COLOR_TEMP_KELVIN in kwargs:
251  data[ATTR_CCT] = kelvin_to_255(
252  kwargs[ATTR_COLOR_TEMP_KELVIN], COLOR_TEMP_K_MIN, COLOR_TEMP_K_MAX
253  )
254 
255  if ATTR_TRANSITION in kwargs:
256  # WLED uses 100ms per unit, so 10 = 1 second.
257  data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
258 
259  if ATTR_BRIGHTNESS in kwargs:
260  data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]
261 
262  if ATTR_EFFECT in kwargs:
263  data[ATTR_EFFECT] = kwargs[ATTR_EFFECT]
264 
265  # If there is no main control, and only 1 segment, handle the main
266  if not self.coordinator.has_main_light:
267  main_data = {ATTR_ON: True}
268  if ATTR_BRIGHTNESS in data:
269  main_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS]
270  data[ATTR_BRIGHTNESS] = 255
271 
272  if ATTR_TRANSITION in data:
273  main_data[ATTR_TRANSITION] = data[ATTR_TRANSITION]
274  del data[ATTR_TRANSITION]
275 
276  await self.coordinator.wled.segment(**data)
277  await self.coordinator.wled.master(**main_data)
278  return
279 
280  await self.coordinator.wled.segment(**data)
281 
282 
283 @callback
285  coordinator: WLEDDataUpdateCoordinator,
286  current_ids: set[int],
287  async_add_entities: AddEntitiesCallback,
288 ) -> None:
289  """Update segments."""
290  segment_ids = {
291  light.segment_id
292  for light in coordinator.data.state.segments.values()
293  if light.segment_id is not None
294  }
295  new_entities: list[WLEDMainLight | WLEDSegmentLight] = []
296 
297  # More than 1 segment now? No main? Add main controls
298  if not coordinator.keep_main_light and (
299  len(current_ids) < 2 and len(segment_ids) > 1
300  ):
301  new_entities.append(WLEDMainLight(coordinator))
302 
303  # Process new segments, add them to Home Assistant
304  for segment_id in segment_ids - current_ids:
305  current_ids.add(segment_id)
306  new_entities.append(WLEDSegmentLight(coordinator, segment_id))
307 
308  async_add_entities(new_entities)
None async_turn_on(self, **Any kwargs)
Definition: light.py:99
None async_turn_off(self, **Any kwargs)
Definition: light.py:89
None __init__(self, WLEDDataUpdateCoordinator coordinator)
Definition: light.py:68
tuple[int, int, int, int]|None rgbw_color(self)
Definition: light.py:169
None __init__(self, WLEDDataUpdateCoordinator coordinator, int segment)
Definition: light.py:123
tuple[int, int, int]|None rgb_color(self)
Definition: light.py:162
int kelvin_to_255(int k, int min_k, int max_k)
Definition: helpers.py:40
int kelvin_to_255_reverse(int v, int min_k, int max_k)
Definition: helpers.py:45
None async_setup_entry(HomeAssistant hass, WLEDConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: light.py:43
None async_update_segments(WLEDDataUpdateCoordinator coordinator, set[int] current_ids, AddEntitiesCallback async_add_entities)
Definition: light.py:288