Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for Z-Wave lights."""
2 
3 from __future__ import annotations
4 
5 from typing import Any, cast
6 
7 from zwave_js_server.client import Client as ZwaveClient
8 from zwave_js_server.const import (
9  TARGET_VALUE_PROPERTY,
10  TRANSITION_DURATION_OPTION,
11  CommandClass,
12 )
13 from zwave_js_server.const.command_class.color_switch import (
14  COLOR_SWITCH_COMBINED_AMBER,
15  COLOR_SWITCH_COMBINED_BLUE,
16  COLOR_SWITCH_COMBINED_COLD_WHITE,
17  COLOR_SWITCH_COMBINED_CYAN,
18  COLOR_SWITCH_COMBINED_GREEN,
19  COLOR_SWITCH_COMBINED_PURPLE,
20  COLOR_SWITCH_COMBINED_RED,
21  COLOR_SWITCH_COMBINED_WARM_WHITE,
22  CURRENT_COLOR_PROPERTY,
23  TARGET_COLOR_PROPERTY,
24  ColorComponent,
25 )
26 from zwave_js_server.const.command_class.multilevel_switch import SET_TO_PREVIOUS_VALUE
27 from zwave_js_server.model.driver import Driver
28 from zwave_js_server.model.value import Value
29 
31  ATTR_BRIGHTNESS,
32  ATTR_COLOR_TEMP,
33  ATTR_HS_COLOR,
34  ATTR_RGBW_COLOR,
35  ATTR_TRANSITION,
36  DOMAIN as LIGHT_DOMAIN,
37  ColorMode,
38  LightEntity,
39  LightEntityFeature,
40 )
41 from homeassistant.config_entries import ConfigEntry
42 from homeassistant.core import HomeAssistant, callback
43 from homeassistant.helpers.dispatcher import async_dispatcher_connect
44 from homeassistant.helpers.entity_platform import AddEntitiesCallback
45 import homeassistant.util.color as color_util
46 
47 from .const import DATA_CLIENT, DOMAIN
48 from .discovery import ZwaveDiscoveryInfo
49 from .entity import ZWaveBaseEntity
50 
51 PARALLEL_UPDATES = 0
52 
53 MULTI_COLOR_MAP = {
54  ColorComponent.WARM_WHITE: COLOR_SWITCH_COMBINED_WARM_WHITE,
55  ColorComponent.COLD_WHITE: COLOR_SWITCH_COMBINED_COLD_WHITE,
56  ColorComponent.RED: COLOR_SWITCH_COMBINED_RED,
57  ColorComponent.GREEN: COLOR_SWITCH_COMBINED_GREEN,
58  ColorComponent.BLUE: COLOR_SWITCH_COMBINED_BLUE,
59  ColorComponent.AMBER: COLOR_SWITCH_COMBINED_AMBER,
60  ColorComponent.CYAN: COLOR_SWITCH_COMBINED_CYAN,
61  ColorComponent.PURPLE: COLOR_SWITCH_COMBINED_PURPLE,
62 }
63 
64 
66  hass: HomeAssistant,
67  config_entry: ConfigEntry,
68  async_add_entities: AddEntitiesCallback,
69 ) -> None:
70  """Set up Z-Wave Light from Config Entry."""
71  client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
72 
73  @callback
74  def async_add_light(info: ZwaveDiscoveryInfo) -> None:
75  """Add Z-Wave Light."""
76  driver = client.driver
77  assert driver is not None # Driver is ready before platforms are loaded.
78 
79  if info.platform_hint == "color_onoff":
80  async_add_entities([ZwaveColorOnOffLight(config_entry, driver, info)])
81  else:
82  async_add_entities([ZwaveLight(config_entry, driver, info)])
83 
84  config_entry.async_on_unload(
86  hass,
87  f"{DOMAIN}_{config_entry.entry_id}_add_{LIGHT_DOMAIN}",
88  async_add_light,
89  )
90  )
91 
92 
93 def byte_to_zwave_brightness(value: int) -> int:
94  """Convert brightness in 0-255 scale to 0-99 scale.
95 
96  `value` -- (int) Brightness byte value from 0-255.
97  """
98  if value > 0:
99  return max(1, round((value / 255) * 99))
100  return 0
101 
102 
104  """Representation of a Z-Wave light."""
105 
106  def __init__(
107  self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
108  ) -> None:
109  """Initialize the light."""
110  super().__init__(config_entry, driver, info)
111  self._supports_color_supports_color = False
112  self._supports_rgbw_supports_rgbw = False
113  self._supports_color_temp_supports_color_temp = False
114  self._supports_dimming_supports_dimming = False
115  self._color_mode_color_mode: str | None = None
116  self._hs_color_hs_color: tuple[float, float] | None = None
117  self._rgbw_color_rgbw_color: tuple[int, int, int, int] | None = None
118  self._color_temp_color_temp: int | None = None
119  self._min_mireds_min_mireds = 153 # 6500K as a safe default
120  self._max_mireds_max_mireds = 370 # 2700K as a safe default
121  self._warm_white_warm_white = self.get_zwave_valueget_zwave_value(
122  TARGET_COLOR_PROPERTY,
123  CommandClass.SWITCH_COLOR,
124  value_property_key=ColorComponent.WARM_WHITE,
125  )
126  self._cold_white_cold_white = self.get_zwave_valueget_zwave_value(
127  TARGET_COLOR_PROPERTY,
128  CommandClass.SWITCH_COLOR,
129  value_property_key=ColorComponent.COLD_WHITE,
130  )
131  self._supported_color_modes: set[ColorMode] = set()
132 
133  self._target_brightness_target_brightness: Value | None = None
134 
135  # get additional (optional) values and set features
136  if self.infoinfo.primary_value.command_class == CommandClass.SWITCH_BINARY:
137  # This light can not be dimmed separately from the color channels
138  self._target_brightness_target_brightness = self.get_zwave_valueget_zwave_value(
139  TARGET_VALUE_PROPERTY,
140  CommandClass.SWITCH_BINARY,
141  add_to_watched_value_ids=False,
142  )
143  self._supports_dimming_supports_dimming = False
144  elif self.infoinfo.primary_value.command_class == CommandClass.SWITCH_MULTILEVEL:
145  # This light can be dimmed separately from the color channels
146  self._target_brightness_target_brightness = self.get_zwave_valueget_zwave_value(
147  TARGET_VALUE_PROPERTY,
148  CommandClass.SWITCH_MULTILEVEL,
149  add_to_watched_value_ids=False,
150  )
151  self._supports_dimming_supports_dimming = True
152  elif self.infoinfo.primary_value.command_class == CommandClass.BASIC:
153  # If the command class is Basic, we must generate a name that includes
154  # the command class name to avoid ambiguity
155  self._attr_name_attr_name_attr_name = self.generate_namegenerate_name(
156  include_value_name=True, alternate_value_name="Basic"
157  )
158  self._target_brightness_target_brightness = self.get_zwave_valueget_zwave_value(
159  TARGET_VALUE_PROPERTY,
160  CommandClass.BASIC,
161  add_to_watched_value_ids=False,
162  )
163  self._supports_dimming_supports_dimming = True
164 
165  self._current_color_current_color = self.get_zwave_valueget_zwave_value(
166  CURRENT_COLOR_PROPERTY,
167  CommandClass.SWITCH_COLOR,
168  value_property_key=None,
169  )
170  self._target_color_target_color = self.get_zwave_valueget_zwave_value(
171  TARGET_COLOR_PROPERTY,
172  CommandClass.SWITCH_COLOR,
173  add_to_watched_value_ids=False,
174  )
175 
176  self._calculate_color_support_calculate_color_support()
177  if self._supports_rgbw_supports_rgbw:
178  self._supported_color_modes.add(ColorMode.RGBW)
179  elif self._supports_color_supports_color:
180  self._supported_color_modes.add(ColorMode.HS)
181  if self._supports_color_temp_supports_color_temp:
182  self._supported_color_modes.add(ColorMode.COLOR_TEMP)
183  if not self._supported_color_modes:
184  self._supported_color_modes.add(ColorMode.BRIGHTNESS)
185  self._calculate_color_values_calculate_color_values()
186 
187  # Entity class attributes
188  self.supports_brightness_transitionsupports_brightness_transition = bool(
189  self._target_brightness_target_brightness is not None
190  and TRANSITION_DURATION_OPTION
191  in self._target_brightness_target_brightness.metadata.value_change_options
192  )
193  self.supports_color_transitionsupports_color_transition = bool(
194  self._target_color_target_color is not None
195  and TRANSITION_DURATION_OPTION
196  in self._target_color_target_color.metadata.value_change_options
197  )
198 
199  if self.supports_brightness_transitionsupports_brightness_transition or self.supports_color_transitionsupports_color_transition:
200  self._attr_supported_features |= LightEntityFeature.TRANSITION
201 
202  self._set_optimistic_state_set_optimistic_state: bool = False
203 
204  @callback
205  def on_value_update(self) -> None:
206  """Call when a watched value is added or updated."""
207  self._calculate_color_values_calculate_color_values()
208 
209  @property
210  def brightness(self) -> int | None:
211  """Return the brightness of this light between 0..255.
212 
213  Z-Wave multilevel switches use a range of [0, 99] to control brightness.
214  """
215  if self.infoinfo.primary_value.value is None:
216  return None
217  return round((cast(int, self.infoinfo.primary_value.value) / 99) * 255)
218 
219  @property
220  def color_mode(self) -> str | None:
221  """Return the color mode of the light."""
222  return self._color_mode_color_mode
223 
224  @property
225  def is_on(self) -> bool | None:
226  """Return true if device is on (brightness above 0)."""
227  if self._set_optimistic_state_set_optimistic_state:
228  self._set_optimistic_state_set_optimistic_state = False
229  return True
230  brightness = self.brightnessbrightnessbrightness
231  return brightness > 0 if brightness is not None else None
232 
233  @property
234  def hs_color(self) -> tuple[float, float] | None:
235  """Return the hs color."""
236  return self._hs_color_hs_color
237 
238  @property
239  def rgbw_color(self) -> tuple[int, int, int, int] | None:
240  """Return the RGBW color."""
241  return self._rgbw_color_rgbw_color
242 
243  @property
244  def color_temp(self) -> int | None:
245  """Return the color temperature."""
246  return self._color_temp_color_temp
247 
248  @property
249  def min_mireds(self) -> int:
250  """Return the coldest color_temp that this light supports."""
251  return self._min_mireds_min_mireds
252 
253  @property
254  def max_mireds(self) -> int:
255  """Return the warmest color_temp that this light supports."""
256  return self._max_mireds_max_mireds
257 
258  @property
259  def supported_color_modes(self) -> set[ColorMode] | None:
260  """Flag supported features."""
261  return self._supported_color_modes
262 
263  async def async_turn_on(self, **kwargs: Any) -> None:
264  """Turn the device on."""
265 
266  transition = kwargs.get(ATTR_TRANSITION)
267  brightness = kwargs.get(ATTR_BRIGHTNESS)
268 
269  hs_color = kwargs.get(ATTR_HS_COLOR)
270  color_temp = kwargs.get(ATTR_COLOR_TEMP)
271  rgbw = kwargs.get(ATTR_RGBW_COLOR)
272 
273  new_colors = self._get_new_colors_get_new_colors(hs_color, color_temp, rgbw)
274  if new_colors is not None:
275  await self._async_set_colors_async_set_colors(new_colors, transition)
276 
277  # set brightness (or turn on if dimming is not supported)
278  await self._async_set_brightness_async_set_brightness(brightness, transition)
279 
280  async def async_turn_off(self, **kwargs: Any) -> None:
281  """Turn the light off."""
282  await self._async_set_brightness_async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
283 
285  self,
286  hs_color: tuple[float, float] | None,
287  color_temp: int | None,
288  rgbw: tuple[int, int, int, int] | None,
289  brightness_scale: float | None = None,
290  ) -> dict[ColorComponent, int] | None:
291  """Determine the new color dict to set."""
292 
293  # RGB/HS color
294  if hs_color is not None and self._supports_color_supports_color:
295  red, green, blue = color_util.color_hs_to_RGB(*hs_color)
296  if brightness_scale is not None:
297  red = round(red * brightness_scale)
298  green = round(green * brightness_scale)
299  blue = round(blue * brightness_scale)
300  colors = {
301  ColorComponent.RED: red,
302  ColorComponent.GREEN: green,
303  ColorComponent.BLUE: blue,
304  }
305  if self._supports_color_temp_supports_color_temp:
306  # turn of white leds when setting rgb
307  colors[ColorComponent.WARM_WHITE] = 0
308  colors[ColorComponent.COLD_WHITE] = 0
309  return colors
310 
311  # Color temperature
312  if color_temp is not None and self._supports_color_temp_supports_color_temp:
313  # Limit color temp to min/max values
314  cold = max(
315  0,
316  min(
317  255,
318  round(
319  (self._max_mireds_max_mireds - color_temp)
320  / (self._max_mireds_max_mireds - self._min_mireds_min_mireds)
321  * 255
322  ),
323  ),
324  )
325  warm = 255 - cold
326  colors = {
327  ColorComponent.WARM_WHITE: warm,
328  ColorComponent.COLD_WHITE: cold,
329  }
330  if self._supports_color_supports_color:
331  # turn off color leds when setting color temperature
332  colors[ColorComponent.RED] = 0
333  colors[ColorComponent.GREEN] = 0
334  colors[ColorComponent.BLUE] = 0
335  return colors
336 
337  # RGBW
338  if rgbw is not None and self._supports_rgbw_supports_rgbw:
339  rgbw_channels = {
340  ColorComponent.RED: rgbw[0],
341  ColorComponent.GREEN: rgbw[1],
342  ColorComponent.BLUE: rgbw[2],
343  }
344  if self._warm_white_warm_white:
345  rgbw_channels[ColorComponent.WARM_WHITE] = rgbw[3]
346 
347  if self._cold_white_cold_white:
348  rgbw_channels[ColorComponent.COLD_WHITE] = rgbw[3]
349 
350  return rgbw_channels
351 
352  return None
353 
354  async def _async_set_colors(
355  self,
356  colors: dict[ColorComponent, int],
357  transition: float | None = None,
358  ) -> None:
359  """Set (multiple) defined colors to given value(s)."""
360  # prefer the (new) combined color property
361  # https://github.com/zwave-js/node-zwave-js/pull/1782
362  # Setting colors is only done if there's a target color value.
363  combined_color_val = cast(
364  Value,
365  self.get_zwave_valueget_zwave_value(
366  "targetColor",
367  CommandClass.SWITCH_COLOR,
368  value_property_key=None,
369  ),
370  )
371  zwave_transition = None
372 
373  if self.supports_color_transitionsupports_color_transition:
374  if transition is not None:
375  zwave_transition = {TRANSITION_DURATION_OPTION: f"{int(transition)}s"}
376  else:
377  zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
378 
379  colors_dict = {}
380  for color, value in colors.items():
381  color_name = MULTI_COLOR_MAP[color]
382  colors_dict[color_name] = value
383  # set updated color object
384  await self._async_set_value_async_set_value(combined_color_val, colors_dict, zwave_transition)
385 
387  self, brightness: int | None, transition: float | None = None
388  ) -> None:
389  """Set new brightness to light."""
390  # If we have no target brightness value, there is nothing to do
391  if not self._target_brightness_target_brightness:
392  return
393  if brightness is None:
394  zwave_brightness = SET_TO_PREVIOUS_VALUE
395  else:
396  # Zwave multilevel switches use a range of [0, 99] to control brightness.
397  zwave_brightness = byte_to_zwave_brightness(brightness)
398 
399  # set transition value before sending new brightness
400  zwave_transition = None
401  if self.supports_brightness_transitionsupports_brightness_transition:
402  if transition is not None:
403  zwave_transition = {TRANSITION_DURATION_OPTION: f"{int(transition)}s"}
404  else:
405  zwave_transition = {TRANSITION_DURATION_OPTION: "default"}
406 
407  # setting a value requires setting targetValue
408  if self._supports_dimming_supports_dimming:
409  await self._async_set_value_async_set_value(
410  self._target_brightness_target_brightness, zwave_brightness, zwave_transition
411  )
412  else:
413  await self._async_set_value_async_set_value(
414  self._target_brightness_target_brightness, zwave_brightness > 0, zwave_transition
415  )
416  # We do an optimistic state update when setting to a previous value
417  # to avoid waiting for the value to be updated from the device which is
418  # typically delayed and causes a confusing UX.
419  if (
420  zwave_brightness == SET_TO_PREVIOUS_VALUE
421  and self.infoinfo.primary_value.command_class
422  in (CommandClass.BASIC, CommandClass.SWITCH_MULTILEVEL)
423  ):
424  self._set_optimistic_state_set_optimistic_state = True
425  self.async_write_ha_stateasync_write_ha_state()
426 
427  @callback
428  def _get_color_values(self) -> tuple[Value | None, ...]:
429  """Get light colors."""
430  # NOTE: We lookup all values here (instead of relying on the multicolor one)
431  # to find out what colors are supported
432  # as this is a simple lookup by key, this not heavy
433  red_val = self.get_zwave_valueget_zwave_value(
434  CURRENT_COLOR_PROPERTY,
435  CommandClass.SWITCH_COLOR,
436  value_property_key=ColorComponent.RED.value,
437  )
438  green_val = self.get_zwave_valueget_zwave_value(
439  CURRENT_COLOR_PROPERTY,
440  CommandClass.SWITCH_COLOR,
441  value_property_key=ColorComponent.GREEN.value,
442  )
443  blue_val = self.get_zwave_valueget_zwave_value(
444  CURRENT_COLOR_PROPERTY,
445  CommandClass.SWITCH_COLOR,
446  value_property_key=ColorComponent.BLUE.value,
447  )
448  ww_val = self.get_zwave_valueget_zwave_value(
449  CURRENT_COLOR_PROPERTY,
450  CommandClass.SWITCH_COLOR,
451  value_property_key=ColorComponent.WARM_WHITE.value,
452  )
453  cw_val = self.get_zwave_valueget_zwave_value(
454  CURRENT_COLOR_PROPERTY,
455  CommandClass.SWITCH_COLOR,
456  value_property_key=ColorComponent.COLD_WHITE.value,
457  )
458  return (red_val, green_val, blue_val, ww_val, cw_val)
459 
460  @callback
461  def _calculate_color_support(self) -> None:
462  """Calculate light colors."""
463  (red, green, blue, warm_white, cool_white) = self._get_color_values_get_color_values()
464  # RGB support
465  if red and green and blue:
466  self._supports_color_supports_color = True
467  # color temperature support
468  if warm_white and cool_white:
469  self._supports_color_temp_supports_color_temp = True
470  # only one white channel (warm white or cool white) = rgbw support
471  elif red and green and blue and warm_white or cool_white:
472  self._supports_rgbw_supports_rgbw = True
473 
474  @callback
475  def _calculate_color_values(self) -> None:
476  """Calculate light colors."""
477  (red_val, green_val, blue_val, ww_val, cw_val) = self._get_color_values_get_color_values()
478 
479  if self._current_color_current_color and isinstance(self._current_color_current_color.value, dict):
480  multi_color = self._current_color_current_color.value
481  else:
482  multi_color = {}
483 
484  # Default: Brightness (no color) or Unknown
485  if self.supported_color_modessupported_color_modessupported_color_modessupported_color_modes == {ColorMode.BRIGHTNESS}:
486  self._color_mode_color_mode = ColorMode.BRIGHTNESS
487  else:
488  self._color_mode_color_mode = ColorMode.UNKNOWN
489 
490  # RGB support
491  if red_val and green_val and blue_val:
492  # prefer values from the multicolor property
493  red = multi_color.get(COLOR_SWITCH_COMBINED_RED, red_val.value)
494  green = multi_color.get(COLOR_SWITCH_COMBINED_GREEN, green_val.value)
495  blue = multi_color.get(COLOR_SWITCH_COMBINED_BLUE, blue_val.value)
496  if None not in (red, green, blue):
497  # convert to HS
498  self._hs_color_hs_color = color_util.color_RGB_to_hs(red, green, blue)
499  # Light supports color, set color mode to hs
500  self._color_mode_color_mode = ColorMode.HS
501 
502  # color temperature support
503  if ww_val and cw_val:
504  warm_white = multi_color.get(COLOR_SWITCH_COMBINED_WARM_WHITE, ww_val.value)
505  cold_white = multi_color.get(COLOR_SWITCH_COMBINED_COLD_WHITE, cw_val.value)
506  # Calculate color temps based on whites
507  if cold_white or warm_white:
508  self._color_temp_color_temp = round(
509  self._max_mireds_max_mireds
510  - ((cold_white / 255) * (self._max_mireds_max_mireds - self._min_mireds_min_mireds))
511  )
512  # White channels turned on, set color mode to color_temp
513  self._color_mode_color_mode = ColorMode.COLOR_TEMP
514  else:
515  self._color_temp_color_temp = None
516  # only one white channel (warm white) = rgbw support
517  elif red_val and green_val and blue_val and ww_val:
518  white = multi_color.get(COLOR_SWITCH_COMBINED_WARM_WHITE, ww_val.value)
519  self._rgbw_color_rgbw_color = (red, green, blue, white)
520  # Light supports rgbw, set color mode to rgbw
521  self._color_mode_color_mode = ColorMode.RGBW
522  # only one white channel (cool white) = rgbw support
523  elif cw_val:
524  self._supports_rgbw_supports_rgbw = True
525  white = multi_color.get(COLOR_SWITCH_COMBINED_COLD_WHITE, cw_val.value)
526  self._rgbw_color_rgbw_color = (red, green, blue, white)
527  # Light supports rgbw, set color mode to rgbw
528  self._color_mode_color_mode = ColorMode.RGBW
529 
530 
532  """Representation of a colored Z-Wave light with an optional binary switch to turn on/off.
533 
534  Dimming for RGB lights is realized by scaling the color channels.
535  """
536 
537  def __init__(
538  self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
539  ) -> None:
540  """Initialize the light."""
541  super().__init__(config_entry, driver, info)
542 
543  self._last_on_color_last_on_color: dict[ColorComponent, int] | None = None
544  self._last_brightness_last_brightness: int | None = None
545 
546  @property
547  def brightness(self) -> int | None:
548  """Return the brightness of this light between 0..255.
549 
550  Z-Wave multilevel switches use a range of [0, 99] to control brightness.
551  """
552  if self.infoinfo.primary_value.value is None:
553  return None
554  if self._target_brightness_target_brightness and self.infoinfo.primary_value.value is False:
555  # Binary switch exists and is turned off
556  return 0
557 
558  # Brightness is encoded in the color channels by scaling them lower than 255
559  color_values = [
560  v.value
561  for v in self._get_color_values_get_color_values()
562  if v is not None and v.value is not None
563  ]
564  return max(color_values) if color_values else 0
565 
566  async def async_turn_on(self, **kwargs: Any) -> None:
567  """Turn the device on."""
568 
569  if (
570  kwargs.get(ATTR_RGBW_COLOR) is not None
571  or kwargs.get(ATTR_COLOR_TEMP) is not None
572  ):
573  # RGBW and color temp are not supported in this mode,
574  # delegate to the parent class
575  await super().async_turn_on(**kwargs)
576  return
577 
578  transition = kwargs.get(ATTR_TRANSITION)
579  brightness = kwargs.get(ATTR_BRIGHTNESS)
580  hs_color = kwargs.get(ATTR_HS_COLOR)
581  new_colors: dict[ColorComponent, int] | None = None
582  scale: float | None = None
583 
584  if brightness is None and hs_color is None:
585  # Turned on without specifying brightness or color
586  if self._last_on_color_last_on_color is not None:
587  if self._target_brightness_target_brightness:
588  # Color is already set, use the binary switch to turn on
589  await self._async_set_brightness_async_set_brightness(None, transition)
590  return
591 
592  # Preserve the previous color
593  new_colors = self._last_on_color_last_on_color
594  elif self._supports_color_supports_color:
595  # Turned on for the first time. Make it white
596  new_colors = {
597  ColorComponent.RED: 255,
598  ColorComponent.GREEN: 255,
599  ColorComponent.BLUE: 255,
600  }
601  elif brightness is not None:
602  # If brightness gets set, preserve the color and mix it with the new brightness
603  if self.color_modecolor_modecolor_modecolor_mode == ColorMode.HS:
604  scale = brightness / 255
605  if (
606  self._last_on_color_last_on_color is not None
607  and None not in self._last_on_color_last_on_color.values()
608  ):
609  # Changed brightness from 0 to >0
610  old_brightness = max(self._last_on_color_last_on_color.values())
611  new_scale = brightness / old_brightness
612  scale = new_scale
613  new_colors = {}
614  for color, value in self._last_on_color_last_on_color.items():
615  new_colors[color] = round(value * new_scale)
616  elif hs_color is None and self._color_mode_color_mode_color_mode == ColorMode.HS:
617  hs_color = self._hs_color_hs_color
618  elif hs_color is not None and brightness is None:
619  # Turned on by using the color controls
620  current_brightness = self.brightnessbrightnessbrightnessbrightness
621  if current_brightness == 0 and self._last_brightness_last_brightness is not None:
622  # Use the last brightness value if the light is currently off
623  scale = self._last_brightness_last_brightness / 255
624  elif current_brightness is not None:
625  scale = current_brightness / 255
626 
627  # Reset last color until turning off again
628  self._last_on_color_last_on_color = None
629 
630  if new_colors is None:
631  new_colors = self._get_new_colors_get_new_colors(
632  hs_color=hs_color, color_temp=None, rgbw=None, brightness_scale=scale
633  )
634 
635  if new_colors is not None:
636  await self._async_set_colors_async_set_colors(new_colors, transition)
637 
638  # Turn the binary switch on if there is one
639  await self._async_set_brightness_async_set_brightness(brightness, transition)
640 
641  async def async_turn_off(self, **kwargs: Any) -> None:
642  """Turn the light off."""
643 
644  # Remember last color and brightness to restore it when turning on
645  self._last_brightness_last_brightness = self.brightnessbrightnessbrightnessbrightness
646  if self._current_color_current_color and isinstance(self._current_color_current_color.value, dict):
647  red = self._current_color_current_color.value.get(COLOR_SWITCH_COMBINED_RED)
648  green = self._current_color_current_color.value.get(COLOR_SWITCH_COMBINED_GREEN)
649  blue = self._current_color_current_color.value.get(COLOR_SWITCH_COMBINED_BLUE)
650 
651  last_color: dict[ColorComponent, int] = {}
652  if red is not None:
653  last_color[ColorComponent.RED] = red
654  if green is not None:
655  last_color[ColorComponent.GREEN] = green
656  if blue is not None:
657  last_color[ColorComponent.BLUE] = blue
658 
659  if last_color:
660  self._last_on_color_last_on_color = last_color
661 
662  if self._target_brightness_target_brightness:
663  # Turn off the binary switch only
664  await self._async_set_brightness_async_set_brightness(0, kwargs.get(ATTR_TRANSITION))
665  else:
666  # turn off all color channels
667  colors = {
668  ColorComponent.RED: 0,
669  ColorComponent.GREEN: 0,
670  ColorComponent.BLUE: 0,
671  }
672 
673  await self._async_set_colors_async_set_colors(
674  colors,
675  kwargs.get(ATTR_TRANSITION),
676  )
set[ColorMode]|set[str]|None supported_color_modes(self)
Definition: __init__.py:1302
ColorMode|str|None color_mode(self)
Definition: __init__.py:909
str generate_name(self, bool include_value_name=False, str|None alternate_value_name=None, Sequence[str|None]|None additional_info=None, str|None name_prefix=None)
Definition: entity.py:163
SetValueResult|None _async_set_value(self, ZwaveValue value, Any new_value, dict|None options=None, bool|None wait_for_result=None)
Definition: entity.py:330
ZwaveValue|None get_zwave_value(self, str|int value_property, int|None command_class=None, int|None endpoint=None, int|str|None value_property_key=None, bool add_to_watched_value_ids=True, bool check_all_endpoints=False)
Definition: entity.py:280
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
Definition: light.py:539
dict[ColorComponent, int]|None _get_new_colors(self, tuple[float, float]|None hs_color, int|None color_temp, tuple[int, int, int, int]|None rgbw, float|None brightness_scale=None)
Definition: light.py:290
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
Definition: light.py:108
tuple[float, float]|None hs_color(self)
Definition: light.py:234
set[ColorMode]|None supported_color_modes(self)
Definition: light.py:259
tuple[Value|None,...] _get_color_values(self)
Definition: light.py:428
None _async_set_colors(self, dict[ColorComponent, int] colors, float|None transition=None)
Definition: light.py:358
tuple[int, int, int, int]|None rgbw_color(self)
Definition: light.py:239
None _async_set_brightness(self, int|None brightness, float|None transition=None)
Definition: light.py:388
bool add(self, _T matcher)
Definition: match.py:185
int byte_to_zwave_brightness(int value)
Definition: light.py:93
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: light.py:69
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103