Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Support for Template lights."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import voluptuous as vol
9 
11  ATTR_BRIGHTNESS,
12  ATTR_COLOR_TEMP,
13  ATTR_EFFECT,
14  ATTR_HS_COLOR,
15  ATTR_RGB_COLOR,
16  ATTR_RGBW_COLOR,
17  ATTR_RGBWW_COLOR,
18  ATTR_TRANSITION,
19  ENTITY_ID_FORMAT,
20  PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
21  ColorMode,
22  LightEntity,
23  LightEntityFeature,
24  filter_supported_color_modes,
25 )
26 from homeassistant.const import (
27  CONF_ENTITY_ID,
28  CONF_FRIENDLY_NAME,
29  CONF_LIGHTS,
30  CONF_UNIQUE_ID,
31  CONF_VALUE_TEMPLATE,
32  STATE_OFF,
33  STATE_ON,
34 )
35 from homeassistant.core import HomeAssistant, callback
36 from homeassistant.exceptions import TemplateError
37 from homeassistant.helpers import config_validation as cv
38 from homeassistant.helpers.entity import async_generate_entity_id
39 from homeassistant.helpers.entity_platform import AddEntitiesCallback
40 from homeassistant.helpers.script import Script
41 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
42 
43 from .const import DOMAIN
44 from .template_entity import (
45  TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
46  TemplateEntity,
47  rewrite_common_legacy_to_modern_conf,
48 )
49 
50 _LOGGER = logging.getLogger(__name__)
51 _VALID_STATES = [STATE_ON, STATE_OFF, "true", "false"]
52 
53 # Legacy
54 CONF_COLOR_ACTION = "set_color"
55 CONF_COLOR_TEMPLATE = "color_template"
56 
57 CONF_HS_ACTION = "set_hs"
58 CONF_HS_TEMPLATE = "hs_template"
59 CONF_RGB_ACTION = "set_rgb"
60 CONF_RGB_TEMPLATE = "rgb_template"
61 CONF_RGBW_ACTION = "set_rgbw"
62 CONF_RGBW_TEMPLATE = "rgbw_template"
63 CONF_RGBWW_ACTION = "set_rgbww"
64 CONF_RGBWW_TEMPLATE = "rgbww_template"
65 CONF_EFFECT_ACTION = "set_effect"
66 CONF_EFFECT_LIST_TEMPLATE = "effect_list_template"
67 CONF_EFFECT_TEMPLATE = "effect_template"
68 CONF_LEVEL_ACTION = "set_level"
69 CONF_LEVEL_TEMPLATE = "level_template"
70 CONF_MAX_MIREDS_TEMPLATE = "max_mireds_template"
71 CONF_MIN_MIREDS_TEMPLATE = "min_mireds_template"
72 CONF_OFF_ACTION = "turn_off"
73 CONF_ON_ACTION = "turn_on"
74 CONF_SUPPORTS_TRANSITION = "supports_transition_template"
75 CONF_TEMPERATURE_ACTION = "set_temperature"
76 CONF_TEMPERATURE_TEMPLATE = "temperature_template"
77 CONF_WHITE_VALUE_ACTION = "set_white_value"
78 CONF_WHITE_VALUE_TEMPLATE = "white_value_template"
79 
80 LIGHT_SCHEMA = vol.All(
81  cv.deprecated(CONF_ENTITY_ID),
82  vol.Schema(
83  {
84  vol.Exclusive(CONF_COLOR_ACTION, "hs_legacy_action"): cv.SCRIPT_SCHEMA,
85  vol.Exclusive(CONF_COLOR_TEMPLATE, "hs_legacy_template"): cv.template,
86  vol.Exclusive(CONF_HS_ACTION, "hs_legacy_action"): cv.SCRIPT_SCHEMA,
87  vol.Exclusive(CONF_HS_TEMPLATE, "hs_legacy_template"): cv.template,
88  vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA,
89  vol.Optional(CONF_RGB_TEMPLATE): cv.template,
90  vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA,
91  vol.Optional(CONF_RGBW_TEMPLATE): cv.template,
92  vol.Optional(CONF_RGBWW_ACTION): cv.SCRIPT_SCHEMA,
93  vol.Optional(CONF_RGBWW_TEMPLATE): cv.template,
94  vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
95  vol.Inclusive(CONF_EFFECT_LIST_TEMPLATE, "effect"): cv.template,
96  vol.Inclusive(CONF_EFFECT_TEMPLATE, "effect"): cv.template,
97  vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
98  vol.Optional(CONF_FRIENDLY_NAME): cv.string,
99  vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA,
100  vol.Optional(CONF_LEVEL_TEMPLATE): cv.template,
101  vol.Optional(CONF_MAX_MIREDS_TEMPLATE): cv.template,
102  vol.Optional(CONF_MIN_MIREDS_TEMPLATE): cv.template,
103  vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
104  vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
105  vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template,
106  vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
107  vol.Optional(CONF_TEMPERATURE_TEMPLATE): cv.template,
108  vol.Optional(CONF_UNIQUE_ID): cv.string,
109  vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
110  }
111  ).extend(TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY.schema),
112 )
113 
114 PLATFORM_SCHEMA = vol.All(
115  # CONF_WHITE_VALUE_* is deprecated, support will be removed in release 2022.9
116  cv.removed(CONF_WHITE_VALUE_ACTION),
117  cv.removed(CONF_WHITE_VALUE_TEMPLATE),
118  LIGHT_PLATFORM_SCHEMA.extend(
119  {vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_SCHEMA)}
120  ),
121 )
122 
123 
124 async def _async_create_entities(hass, config):
125  """Create the Template Lights."""
126  lights = []
127 
128  for object_id, entity_config in config[CONF_LIGHTS].items():
129  entity_config = rewrite_common_legacy_to_modern_conf(hass, entity_config)
130  unique_id = entity_config.get(CONF_UNIQUE_ID)
131 
132  lights.append(
134  hass,
135  object_id,
136  entity_config,
137  unique_id,
138  )
139  )
140 
141  return lights
142 
143 
145  hass: HomeAssistant,
146  config: ConfigType,
147  async_add_entities: AddEntitiesCallback,
148  discovery_info: DiscoveryInfoType | None = None,
149 ) -> None:
150  """Set up the template lights."""
151  async_add_entities(await _async_create_entities(hass, config))
152 
153 
155  """Representation of a templated Light, including dimmable."""
156 
157  _attr_should_poll = False
158 
159  def __init__(
160  self,
161  hass,
162  object_id,
163  config,
164  unique_id,
165  ):
166  """Initialize the light."""
167  super().__init__(
168  hass, config=config, fallback_name=object_id, unique_id=unique_id
169  )
171  ENTITY_ID_FORMAT, object_id, hass=hass
172  )
173  friendly_name = self._attr_name_attr_name
174  self._template_template = config.get(CONF_VALUE_TEMPLATE)
175  self._on_script_on_script = Script(hass, config[CONF_ON_ACTION], friendly_name, DOMAIN)
176  self._off_script_off_script = Script(hass, config[CONF_OFF_ACTION], friendly_name, DOMAIN)
177  self._level_script_level_script = None
178  if (level_action := config.get(CONF_LEVEL_ACTION)) is not None:
179  self._level_script_level_script = Script(hass, level_action, friendly_name, DOMAIN)
180  self._level_template_level_template = config.get(CONF_LEVEL_TEMPLATE)
181  self._temperature_script_temperature_script = None
182  if (temperature_action := config.get(CONF_TEMPERATURE_ACTION)) is not None:
183  self._temperature_script_temperature_script = Script(
184  hass, temperature_action, friendly_name, DOMAIN
185  )
186  self._temperature_template_temperature_template = config.get(CONF_TEMPERATURE_TEMPLATE)
187  self._color_script_color_script = None
188  if (color_action := config.get(CONF_COLOR_ACTION)) is not None:
189  self._color_script_color_script = Script(hass, color_action, friendly_name, DOMAIN)
190  self._color_template_color_template = config.get(CONF_COLOR_TEMPLATE)
191  self._hs_script_hs_script = None
192  if (hs_action := config.get(CONF_HS_ACTION)) is not None:
193  self._hs_script_hs_script = Script(hass, hs_action, friendly_name, DOMAIN)
194  self._hs_template_hs_template = config.get(CONF_HS_TEMPLATE)
195  self._rgb_script_rgb_script = None
196  if (rgb_action := config.get(CONF_RGB_ACTION)) is not None:
197  self._rgb_script_rgb_script = Script(hass, rgb_action, friendly_name, DOMAIN)
198  self._rgb_template_rgb_template = config.get(CONF_RGB_TEMPLATE)
199  self._rgbw_script_rgbw_script = None
200  if (rgbw_action := config.get(CONF_RGBW_ACTION)) is not None:
201  self._rgbw_script_rgbw_script = Script(hass, rgbw_action, friendly_name, DOMAIN)
202  self._rgbw_template_rgbw_template = config.get(CONF_RGBW_TEMPLATE)
203  self._rgbww_script_rgbww_script = None
204  if (rgbww_action := config.get(CONF_RGBWW_ACTION)) is not None:
205  self._rgbww_script_rgbww_script = Script(hass, rgbww_action, friendly_name, DOMAIN)
206  self._rgbww_template_rgbww_template = config.get(CONF_RGBWW_TEMPLATE)
207  self._effect_script_effect_script = None
208  if (effect_action := config.get(CONF_EFFECT_ACTION)) is not None:
209  self._effect_script_effect_script = Script(hass, effect_action, friendly_name, DOMAIN)
210  self._effect_list_template_effect_list_template = config.get(CONF_EFFECT_LIST_TEMPLATE)
211  self._effect_template_effect_template = config.get(CONF_EFFECT_TEMPLATE)
212  self._max_mireds_template_max_mireds_template = config.get(CONF_MAX_MIREDS_TEMPLATE)
213  self._min_mireds_template_min_mireds_template = config.get(CONF_MIN_MIREDS_TEMPLATE)
214  self._supports_transition_template_supports_transition_template = config.get(CONF_SUPPORTS_TRANSITION)
215 
216  self._state_state = False
217  self._brightness_brightness = None
218  self._temperature_temperature = None
219  self._hs_color_hs_color = None
220  self._rgb_color_rgb_color = None
221  self._rgbw_color_rgbw_color = None
222  self._rgbww_color_rgbww_color = None
223  self._effect_effect = None
224  self._effect_list_effect_list = None
225  self._color_mode_color_mode = None
226  self._max_mireds_max_mireds = None
227  self._min_mireds_min_mireds = None
228  self._supports_transition_supports_transition = False
229  self._supported_color_modes_supported_color_modes = None
230 
231  color_modes = {ColorMode.ONOFF}
232  if self._level_script_level_script is not None:
233  color_modes.add(ColorMode.BRIGHTNESS)
234  if self._temperature_script_temperature_script is not None:
235  color_modes.add(ColorMode.COLOR_TEMP)
236  if self._hs_script_hs_script is not None:
237  color_modes.add(ColorMode.HS)
238  if self._color_script_color_script is not None:
239  color_modes.add(ColorMode.HS)
240  if self._rgb_script_rgb_script is not None:
241  color_modes.add(ColorMode.RGB)
242  if self._rgbw_script_rgbw_script is not None:
243  color_modes.add(ColorMode.RGBW)
244  if self._rgbww_script_rgbww_script is not None:
245  color_modes.add(ColorMode.RGBWW)
246 
247  self._supported_color_modes_supported_color_modes = filter_supported_color_modes(color_modes)
248  if len(self._supported_color_modes_supported_color_modes) > 1:
249  self._color_mode_color_mode = ColorMode.UNKNOWN
250  if len(self._supported_color_modes_supported_color_modes) == 1:
251  self._color_mode_color_mode = next(iter(self._supported_color_modes_supported_color_modes))
252 
253  self._attr_supported_features_attr_supported_features = LightEntityFeature(0)
254  if self._effect_script_effect_script is not None:
255  self._attr_supported_features_attr_supported_features |= LightEntityFeature.EFFECT
256  if self._supports_transition_supports_transition is True:
257  self._attr_supported_features_attr_supported_features |= LightEntityFeature.TRANSITION
258 
259  @property
260  def brightness(self) -> int | None:
261  """Return the brightness of the light."""
262  return self._brightness_brightness
263 
264  @property
265  def color_temp(self) -> int | None:
266  """Return the CT color value in mireds."""
267  return self._temperature_temperature
268 
269  @property
270  def max_mireds(self) -> int:
271  """Return the max mireds value in mireds."""
272  if self._max_mireds_max_mireds is not None:
273  return self._max_mireds_max_mireds
274 
275  return super().max_mireds
276 
277  @property
278  def min_mireds(self) -> int:
279  """Return the min mireds value in mireds."""
280  if self._min_mireds_min_mireds is not None:
281  return self._min_mireds_min_mireds
282 
283  return super().min_mireds
284 
285  @property
286  def hs_color(self) -> tuple[float, float] | None:
287  """Return the hue and saturation color value [float, float]."""
288  return self._hs_color_hs_color
289 
290  @property
291  def rgb_color(self) -> tuple[int, int, int] | None:
292  """Return the rgb color value."""
293  return self._rgb_color_rgb_color
294 
295  @property
296  def rgbw_color(self) -> tuple[int, int, int, int] | None:
297  """Return the rgbw color value."""
298  return self._rgbw_color_rgbw_color
299 
300  @property
301  def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
302  """Return the rgbww color value."""
303  return self._rgbww_color_rgbww_color
304 
305  @property
306  def effect(self) -> str | None:
307  """Return the effect."""
308  return self._effect_effect
309 
310  @property
311  def effect_list(self) -> list[str] | None:
312  """Return the effect list."""
313  return self._effect_list_effect_list
314 
315  @property
316  def color_mode(self):
317  """Return current color mode."""
318  return self._color_mode_color_mode
319 
320  @property
322  """Flag supported color modes."""
323  return self._supported_color_modes_supported_color_modes
324 
325  @property
326  def is_on(self) -> bool | None:
327  """Return true if device is on."""
328  return self._state_state
329 
330  @callback
331  def _async_setup_templates(self) -> None:
332  """Set up templates."""
333  if self._template_template:
334  self.add_template_attributeadd_template_attribute(
335  "_state", self._template_template, None, self._update_state_update_state_update_state
336  )
337  if self._level_template_level_template:
338  self.add_template_attributeadd_template_attribute(
339  "_brightness",
340  self._level_template_level_template,
341  None,
342  self._update_brightness_update_brightness,
343  none_on_template_error=True,
344  )
345  if self._max_mireds_template_max_mireds_template:
346  self.add_template_attributeadd_template_attribute(
347  "_max_mireds_template",
348  self._max_mireds_template_max_mireds_template,
349  None,
350  self._update_max_mireds_update_max_mireds,
351  none_on_template_error=True,
352  )
353  if self._min_mireds_template_min_mireds_template:
354  self.add_template_attributeadd_template_attribute(
355  "_min_mireds_template",
356  self._min_mireds_template_min_mireds_template,
357  None,
358  self._update_min_mireds_update_min_mireds,
359  none_on_template_error=True,
360  )
361  if self._temperature_template_temperature_template:
362  self.add_template_attributeadd_template_attribute(
363  "_temperature",
364  self._temperature_template_temperature_template,
365  None,
366  self._update_temperature_update_temperature,
367  none_on_template_error=True,
368  )
369  if self._color_template_color_template:
370  self.add_template_attributeadd_template_attribute(
371  "_hs_color",
372  self._color_template_color_template,
373  None,
374  self._update_hs_update_hs,
375  none_on_template_error=True,
376  )
377  if self._hs_template_hs_template:
378  self.add_template_attributeadd_template_attribute(
379  "_hs_color",
380  self._hs_template_hs_template,
381  None,
382  self._update_hs_update_hs,
383  none_on_template_error=True,
384  )
385  if self._rgb_template_rgb_template:
386  self.add_template_attributeadd_template_attribute(
387  "_rgb_color",
388  self._rgb_template_rgb_template,
389  None,
390  self._update_rgb_update_rgb,
391  none_on_template_error=True,
392  )
393  if self._rgbw_template_rgbw_template:
394  self.add_template_attributeadd_template_attribute(
395  "_rgbw_color",
396  self._rgbw_template_rgbw_template,
397  None,
398  self._update_rgbw_update_rgbw,
399  none_on_template_error=True,
400  )
401  if self._rgbww_template_rgbww_template:
402  self.add_template_attributeadd_template_attribute(
403  "_rgbww_color",
404  self._rgbww_template_rgbww_template,
405  None,
406  self._update_rgbww_update_rgbww,
407  none_on_template_error=True,
408  )
409  if self._effect_list_template_effect_list_template:
410  self.add_template_attributeadd_template_attribute(
411  "_effect_list",
412  self._effect_list_template_effect_list_template,
413  None,
414  self._update_effect_list_update_effect_list,
415  none_on_template_error=True,
416  )
417  if self._effect_template_effect_template:
418  self.add_template_attributeadd_template_attribute(
419  "_effect",
420  self._effect_template_effect_template,
421  None,
422  self._update_effect_update_effect,
423  none_on_template_error=True,
424  )
425  if self._supports_transition_template_supports_transition_template:
426  self.add_template_attributeadd_template_attribute(
427  "_supports_transition_template",
428  self._supports_transition_template_supports_transition_template,
429  None,
430  self._update_supports_transition_update_supports_transition,
431  none_on_template_error=True,
432  )
433  super()._async_setup_templates()
434 
435  async def async_turn_on(self, **kwargs: Any) -> None: # noqa: C901
436  """Turn the light on."""
437  optimistic_set = False
438  # set optimistic states
439  if self._template_template is None:
440  self._state_state = True
441  optimistic_set = True
442 
443  if self._level_template_level_template is None and ATTR_BRIGHTNESS in kwargs:
444  _LOGGER.debug(
445  "Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS]
446  )
447  self._brightness_brightness = kwargs[ATTR_BRIGHTNESS]
448  optimistic_set = True
449 
450  if self._temperature_template_temperature_template is None and ATTR_COLOR_TEMP in kwargs:
451  _LOGGER.debug(
452  "Optimistically setting color temperature to %s",
453  kwargs[ATTR_COLOR_TEMP],
454  )
455  self._color_mode_color_mode = ColorMode.COLOR_TEMP
456  self._temperature_temperature = kwargs[ATTR_COLOR_TEMP]
457  if self._hs_template_hs_template is None and self._color_template_color_template is None:
458  self._hs_color_hs_color = None
459  if self._rgb_template_rgb_template is None:
460  self._rgb_color_rgb_color = None
461  if self._rgbw_template_rgbw_template is None:
462  self._rgbw_color_rgbw_color = None
463  if self._rgbww_template_rgbww_template is None:
464  self._rgbww_color_rgbww_color = None
465  optimistic_set = True
466 
467  if (
468  self._hs_template_hs_template is None
469  and self._color_template_color_template is None
470  and ATTR_HS_COLOR in kwargs
471  ):
472  _LOGGER.debug(
473  "Optimistically setting hs color to %s",
474  kwargs[ATTR_HS_COLOR],
475  )
476  self._color_mode_color_mode = ColorMode.HS
477  self._hs_color_hs_color = kwargs[ATTR_HS_COLOR]
478  if self._temperature_template_temperature_template is None:
479  self._temperature_temperature = None
480  if self._rgb_template_rgb_template is None:
481  self._rgb_color_rgb_color = None
482  if self._rgbw_template_rgbw_template is None:
483  self._rgbw_color_rgbw_color = None
484  if self._rgbww_template_rgbww_template is None:
485  self._rgbww_color_rgbww_color = None
486  optimistic_set = True
487 
488  if self._rgb_template_rgb_template is None and ATTR_RGB_COLOR in kwargs:
489  _LOGGER.debug(
490  "Optimistically setting rgb color to %s",
491  kwargs[ATTR_RGB_COLOR],
492  )
493  self._color_mode_color_mode = ColorMode.RGB
494  self._rgb_color_rgb_color = kwargs[ATTR_RGB_COLOR]
495  if self._temperature_template_temperature_template is None:
496  self._temperature_temperature = None
497  if self._hs_template_hs_template is None and self._color_template_color_template is None:
498  self._hs_color_hs_color = None
499  if self._rgbw_template_rgbw_template is None:
500  self._rgbw_color_rgbw_color = None
501  if self._rgbww_template_rgbww_template is None:
502  self._rgbww_color_rgbww_color = None
503  optimistic_set = True
504 
505  if self._rgbw_template_rgbw_template is None and ATTR_RGBW_COLOR in kwargs:
506  _LOGGER.debug(
507  "Optimistically setting rgbw color to %s",
508  kwargs[ATTR_RGBW_COLOR],
509  )
510  self._color_mode_color_mode = ColorMode.RGBW
511  self._rgbw_color_rgbw_color = kwargs[ATTR_RGBW_COLOR]
512  if self._temperature_template_temperature_template is None:
513  self._temperature_temperature = None
514  if self._hs_template_hs_template is None and self._color_template_color_template is None:
515  self._hs_color_hs_color = None
516  if self._rgb_template_rgb_template is None:
517  self._rgb_color_rgb_color = None
518  if self._rgbww_template_rgbww_template is None:
519  self._rgbww_color_rgbww_color = None
520  optimistic_set = True
521 
522  if self._rgbww_template_rgbww_template is None and ATTR_RGBWW_COLOR in kwargs:
523  _LOGGER.debug(
524  "Optimistically setting rgbww color to %s",
525  kwargs[ATTR_RGBWW_COLOR],
526  )
527  self._color_mode_color_mode = ColorMode.RGBWW
528  self._rgbww_color_rgbww_color = kwargs[ATTR_RGBWW_COLOR]
529  if self._temperature_template_temperature_template is None:
530  self._temperature_temperature = None
531  if self._hs_template_hs_template is None and self._color_template_color_template is None:
532  self._hs_color_hs_color = None
533  if self._rgb_template_rgb_template is None:
534  self._rgb_color_rgb_color = None
535  if self._rgbw_template_rgbw_template is None:
536  self._rgbw_color_rgbw_color = None
537  optimistic_set = True
538 
539  common_params = {}
540 
541  if ATTR_BRIGHTNESS in kwargs:
542  common_params["brightness"] = kwargs[ATTR_BRIGHTNESS]
543 
544  if ATTR_TRANSITION in kwargs and self._supports_transition_supports_transition is True:
545  common_params["transition"] = kwargs[ATTR_TRANSITION]
546 
547  if ATTR_COLOR_TEMP in kwargs and self._temperature_script_temperature_script:
548  common_params["color_temp"] = kwargs[ATTR_COLOR_TEMP]
549 
550  await self.async_run_scriptasync_run_script(
551  self._temperature_script_temperature_script,
552  run_variables=common_params,
553  context=self._context_context,
554  )
555  elif ATTR_EFFECT in kwargs and self._effect_script_effect_script:
556  effect = kwargs[ATTR_EFFECT]
557  if effect not in self._effect_list_effect_list:
558  _LOGGER.error(
559  "Received invalid effect: %s for entity %s. Expected one of: %s",
560  effect,
561  self.entity_identity_identity_identity_id,
562  self._effect_list_effect_list,
563  exc_info=True,
564  )
565 
566  common_params["effect"] = effect
567 
568  await self.async_run_scriptasync_run_script(
569  self._effect_script_effect_script, run_variables=common_params, context=self._context_context
570  )
571  elif ATTR_HS_COLOR in kwargs and self._color_script_color_script:
572  hs_value = kwargs[ATTR_HS_COLOR]
573  common_params["hs"] = hs_value
574  common_params["h"] = int(hs_value[0])
575  common_params["s"] = int(hs_value[1])
576 
577  await self.async_run_scriptasync_run_script(
578  self._color_script_color_script, run_variables=common_params, context=self._context_context
579  )
580  elif ATTR_HS_COLOR in kwargs and self._hs_script_hs_script:
581  hs_value = kwargs[ATTR_HS_COLOR]
582  common_params["hs"] = hs_value
583  common_params["h"] = int(hs_value[0])
584  common_params["s"] = int(hs_value[1])
585 
586  await self.async_run_scriptasync_run_script(
587  self._hs_script_hs_script, run_variables=common_params, context=self._context_context
588  )
589  elif ATTR_RGBWW_COLOR in kwargs and self._rgbww_script_rgbww_script:
590  rgbww_value = kwargs[ATTR_RGBWW_COLOR]
591  common_params["rgbww"] = rgbww_value
592  common_params["rgb"] = (
593  int(rgbww_value[0]),
594  int(rgbww_value[1]),
595  int(rgbww_value[2]),
596  )
597  common_params["r"] = int(rgbww_value[0])
598  common_params["g"] = int(rgbww_value[1])
599  common_params["b"] = int(rgbww_value[2])
600  common_params["cw"] = int(rgbww_value[3])
601  common_params["ww"] = int(rgbww_value[4])
602 
603  await self.async_run_scriptasync_run_script(
604  self._rgbww_script_rgbww_script, run_variables=common_params, context=self._context_context
605  )
606  elif ATTR_RGBW_COLOR in kwargs and self._rgbw_script_rgbw_script:
607  rgbw_value = kwargs[ATTR_RGBW_COLOR]
608  common_params["rgbw"] = rgbw_value
609  common_params["rgb"] = (
610  int(rgbw_value[0]),
611  int(rgbw_value[1]),
612  int(rgbw_value[2]),
613  )
614  common_params["r"] = int(rgbw_value[0])
615  common_params["g"] = int(rgbw_value[1])
616  common_params["b"] = int(rgbw_value[2])
617  common_params["w"] = int(rgbw_value[3])
618 
619  await self.async_run_scriptasync_run_script(
620  self._rgbw_script_rgbw_script, run_variables=common_params, context=self._context_context
621  )
622  elif ATTR_RGB_COLOR in kwargs and self._rgb_script_rgb_script:
623  rgb_value = kwargs[ATTR_RGB_COLOR]
624  common_params["rgb"] = rgb_value
625  common_params["r"] = int(rgb_value[0])
626  common_params["g"] = int(rgb_value[1])
627  common_params["b"] = int(rgb_value[2])
628 
629  await self.async_run_scriptasync_run_script(
630  self._rgb_script_rgb_script, run_variables=common_params, context=self._context_context
631  )
632  elif ATTR_BRIGHTNESS in kwargs and self._level_script_level_script:
633  await self.async_run_scriptasync_run_script(
634  self._level_script_level_script, run_variables=common_params, context=self._context_context
635  )
636  else:
637  await self.async_run_scriptasync_run_script(
638  self._on_script_on_script, run_variables=common_params, context=self._context_context
639  )
640 
641  if optimistic_set:
642  self.async_write_ha_stateasync_write_ha_state()
643 
644  async def async_turn_off(self, **kwargs: Any) -> None:
645  """Turn the light off."""
646  if ATTR_TRANSITION in kwargs and self._supports_transition_supports_transition is True:
647  await self.async_run_scriptasync_run_script(
648  self._off_script_off_script,
649  run_variables={"transition": kwargs[ATTR_TRANSITION]},
650  context=self._context_context,
651  )
652  else:
653  await self.async_run_scriptasync_run_script(self._off_script_off_script, context=self._context_context)
654  if self._template_template is None:
655  self._state_state = False
656  self.async_write_ha_stateasync_write_ha_state()
657 
658  @callback
659  def _update_brightness(self, brightness):
660  """Update the brightness from the template."""
661  try:
662  if brightness in (None, "None", ""):
663  self._brightness_brightness = None
664  return
665  if 0 <= int(brightness) <= 255:
666  self._brightness_brightness = int(brightness)
667  else:
668  _LOGGER.error(
669  "Received invalid brightness : %s for entity %s. Expected: 0-255",
670  brightness,
671  self.entity_identity_identity_identity_id,
672  )
673  self._brightness_brightness = None
674  except ValueError:
675  _LOGGER.exception(
676  "Template must supply an integer brightness from 0-255, or 'None'"
677  )
678  self._brightness_brightness = None
679 
680  @callback
681  def _update_effect_list(self, effect_list):
682  """Update the effect list from the template."""
683  if effect_list in (None, "None", ""):
684  self._effect_list_effect_list = None
685  return
686 
687  if not isinstance(effect_list, list):
688  _LOGGER.error(
689  (
690  "Received invalid effect list: %s for entity %s. Expected list of"
691  " strings"
692  ),
693  effect_list,
694  self.entity_identity_identity_identity_id,
695  )
696  self._effect_list_effect_list = None
697  return
698 
699  if len(effect_list) == 0:
700  self._effect_list_effect_list = None
701  return
702 
703  self._effect_list_effect_list = effect_list
704 
705  @callback
706  def _update_effect(self, effect):
707  """Update the effect from the template."""
708  if effect in (None, "None", ""):
709  self._effect_effect = None
710  return
711 
712  if effect not in self._effect_list_effect_list:
713  _LOGGER.error(
714  "Received invalid effect: %s for entity %s. Expected one of: %s",
715  effect,
716  self.entity_identity_identity_identity_id,
717  self._effect_list_effect_list,
718  )
719  self._effect_effect = None
720  return
721 
722  self._effect_effect = effect
723 
724  @callback
725  def _update_state(self, result):
726  """Update the state from the template."""
727  if isinstance(result, TemplateError):
728  # This behavior is legacy
729  self._state_state = False
730  if not self._availability_template_availability_template:
732  return
733 
734  if isinstance(result, bool):
735  self._state_state = result
736  return
737 
738  state = str(result).lower()
739  if state in _VALID_STATES:
740  self._state_state = state in ("true", STATE_ON)
741  return
742 
743  _LOGGER.error(
744  "Received invalid light is_on state: %s for entity %s. Expected: %s",
745  state,
746  self.entity_identity_identity_identity_id,
747  ", ".join(_VALID_STATES),
748  )
749  self._state_state = None
750 
751  @callback
752  def _update_temperature(self, render):
753  """Update the temperature from the template."""
754  try:
755  if render in (None, "None", ""):
756  self._temperature_temperature = None
757  return
758  temperature = int(render)
759  if self.min_miredsmin_miredsmin_mireds <= temperature <= self.max_miredsmax_miredsmax_mireds:
760  self._temperature_temperature = temperature
761  else:
762  _LOGGER.error(
763  (
764  "Received invalid color temperature : %s for entity %s."
765  " Expected: %s-%s"
766  ),
767  temperature,
768  self.entity_identity_identity_identity_id,
769  self.min_miredsmin_miredsmin_mireds,
770  self.max_miredsmax_miredsmax_mireds,
771  )
772  self._temperature_temperature = None
773  except ValueError:
774  _LOGGER.exception(
775  "Template must supply an integer temperature within the range for"
776  " this light, or 'None'"
777  )
778  self._temperature_temperature = None
779  self._color_mode_color_mode = ColorMode.COLOR_TEMP
780 
781  @callback
782  def _update_hs(self, render):
783  """Update the color from the template."""
784  if render is None:
785  self._hs_color_hs_color = None
786  return
787 
788  h_str = s_str = None
789  if isinstance(render, str):
790  if render in ("None", ""):
791  self._hs_color_hs_color = None
792  return
793  h_str, s_str = map(
794  float, render.replace("(", "").replace(")", "").split(",", 1)
795  )
796  elif isinstance(render, (list, tuple)) and len(render) == 2:
797  h_str, s_str = render
798 
799  if (
800  h_str is not None
801  and s_str is not None
802  and isinstance(h_str, (int, float))
803  and isinstance(s_str, (int, float))
804  and 0 <= h_str <= 360
805  and 0 <= s_str <= 100
806  ):
807  self._hs_color_hs_color = (h_str, s_str)
808  elif h_str is not None and s_str is not None:
809  _LOGGER.error(
810  (
811  "Received invalid hs_color : (%s, %s) for entity %s. Expected:"
812  " (0-360, 0-100)"
813  ),
814  h_str,
815  s_str,
816  self.entity_identity_identity_identity_id,
817  )
818  self._hs_color_hs_color = None
819  else:
820  _LOGGER.error(
821  "Received invalid hs_color : (%s) for entity %s", render, self.entity_identity_identity_identity_id
822  )
823  self._hs_color_hs_color = None
824  self._color_mode_color_mode = ColorMode.HS
825 
826  @callback
827  def _update_rgb(self, render):
828  """Update the color from the template."""
829  if render is None:
830  self._rgb_color_rgb_color = None
831  return
832 
833  r_int = g_int = b_int = None
834  if isinstance(render, str):
835  if render in ("None", ""):
836  self._rgb_color_rgb_color = None
837  return
838  cleanup_char = ["(", ")", "[", "]", " "]
839  for char in cleanup_char:
840  render = render.replace(char, "")
841  r_int, g_int, b_int = map(int, render.split(",", 3))
842  elif isinstance(render, (list, tuple)) and len(render) == 3:
843  r_int, g_int, b_int = render
844 
845  if all(
846  value is not None and isinstance(value, (int, float)) and 0 <= value <= 255
847  for value in (r_int, g_int, b_int)
848  ):
849  self._rgb_color_rgb_color = (r_int, g_int, b_int)
850  elif any(
851  isinstance(value, (int, float)) and not 0 <= value <= 255
852  for value in (r_int, g_int, b_int)
853  ):
854  _LOGGER.error(
855  "Received invalid rgb_color : (%s, %s, %s) for entity %s. Expected: (0-255, 0-255, 0-255)",
856  r_int,
857  g_int,
858  b_int,
859  self.entity_identity_identity_identity_id,
860  )
861  self._rgb_color_rgb_color = None
862  else:
863  _LOGGER.error(
864  "Received invalid rgb_color : (%s) for entity %s",
865  render,
866  self.entity_identity_identity_identity_id,
867  )
868  self._rgb_color_rgb_color = None
869  self._color_mode_color_mode = ColorMode.RGB
870 
871  @callback
872  def _update_rgbw(self, render):
873  """Update the color from the template."""
874  if render is None:
875  self._rgbw_color_rgbw_color = None
876  return
877 
878  r_int = g_int = b_int = w_int = None
879  if isinstance(render, str):
880  if render in ("None", ""):
881  self._rgb_color_rgb_color = None
882  return
883  cleanup_char = ["(", ")", "[", "]", " "]
884  for char in cleanup_char:
885  render = render.replace(char, "")
886  r_int, g_int, b_int, w_int = map(int, render.split(",", 4))
887  elif isinstance(render, (list, tuple)) and len(render) == 4:
888  r_int, g_int, b_int, w_int = render
889 
890  if all(
891  value is not None and isinstance(value, (int, float)) and 0 <= value <= 255
892  for value in (r_int, g_int, b_int, w_int)
893  ):
894  self._rgbw_color_rgbw_color = (r_int, g_int, b_int, w_int)
895  elif any(
896  isinstance(value, (int, float)) and not 0 <= value <= 255
897  for value in (r_int, g_int, b_int, w_int)
898  ):
899  _LOGGER.error(
900  "Received invalid rgb_color : (%s, %s, %s, %s) for entity %s. Expected: (0-255, 0-255, 0-255, 0-255)",
901  r_int,
902  g_int,
903  b_int,
904  w_int,
905  self.entity_identity_identity_identity_id,
906  )
907  self._rgbw_color_rgbw_color = None
908  else:
909  _LOGGER.error(
910  "Received invalid rgb_color : (%s) for entity %s",
911  render,
912  self.entity_identity_identity_identity_id,
913  )
914  self._rgbw_color_rgbw_color = None
915  self._color_mode_color_mode = ColorMode.RGBW
916 
917  @callback
918  def _update_rgbww(self, render):
919  """Update the color from the template."""
920  if render is None:
921  self._rgbww_color_rgbww_color = None
922  return
923 
924  r_int = g_int = b_int = cw_int = ww_int = None
925  if isinstance(render, str):
926  if render in ("None", ""):
927  self._rgb_color_rgb_color = None
928  return
929  cleanup_char = ["(", ")", "[", "]", " "]
930  for char in cleanup_char:
931  render = render.replace(char, "")
932  r_int, g_int, b_int, cw_int, ww_int = map(int, render.split(",", 5))
933  elif isinstance(render, (list, tuple)) and len(render) == 5:
934  r_int, g_int, b_int, cw_int, ww_int = render
935 
936  if all(
937  value is not None and isinstance(value, (int, float)) and 0 <= value <= 255
938  for value in (r_int, g_int, b_int, cw_int, ww_int)
939  ):
940  self._rgbww_color_rgbww_color = (r_int, g_int, b_int, cw_int, ww_int)
941  elif any(
942  isinstance(value, (int, float)) and not 0 <= value <= 255
943  for value in (r_int, g_int, b_int, cw_int, ww_int)
944  ):
945  _LOGGER.error(
946  "Received invalid rgb_color : (%s, %s, %s, %s, %s) for entity %s. Expected: (0-255, 0-255, 0-255, 0-255)",
947  r_int,
948  g_int,
949  b_int,
950  cw_int,
951  ww_int,
952  self.entity_identity_identity_identity_id,
953  )
954  self._rgbww_color_rgbww_color = None
955  else:
956  _LOGGER.error(
957  "Received invalid rgb_color : (%s) for entity %s",
958  render,
959  self.entity_identity_identity_identity_id,
960  )
961  self._rgbww_color_rgbww_color = None
962  self._color_mode_color_mode = ColorMode.RGBWW
963 
964  @callback
965  def _update_max_mireds(self, render):
966  """Update the max mireds from the template."""
967 
968  try:
969  if render in (None, "None", ""):
970  self._max_mireds_max_mireds = None
971  return
972  self._max_mireds_max_mireds = int(render)
973  except ValueError:
974  _LOGGER.exception(
975  "Template must supply an integer temperature within the range for"
976  " this light, or 'None'"
977  )
978  self._max_mireds_max_mireds = None
979 
980  @callback
981  def _update_min_mireds(self, render):
982  """Update the min mireds from the template."""
983  try:
984  if render in (None, "None", ""):
985  self._min_mireds_min_mireds = None
986  return
987  self._min_mireds_min_mireds = int(render)
988  except ValueError:
989  _LOGGER.exception(
990  "Template must supply an integer temperature within the range for"
991  " this light, or 'None'"
992  )
993  self._min_mireds_min_mireds = None
994 
995  @callback
996  def _update_supports_transition(self, render):
997  """Update the supports transition from the template."""
998  if render in (None, "None", ""):
999  self._supports_transition_supports_transition = False
1000  return
1001  self._attr_supported_features_attr_supported_features &= LightEntityFeature.EFFECT
1002  self._supports_transition_supports_transition = bool(render)
1003  if self._supports_transition_supports_transition:
1004  self._attr_supported_features_attr_supported_features |= LightEntityFeature.TRANSITION
tuple[float, float]|None hs_color(self)
Definition: light.py:286
def __init__(self, hass, object_id, config, unique_id)
Definition: light.py:165
tuple[int, int, int]|None rgb_color(self)
Definition: light.py:291
tuple[int, int, int, int]|None rgbw_color(self)
Definition: light.py:296
tuple[int, int, int, int, int]|None rgbww_color(self)
Definition: light.py:301
None async_run_script(self, Script script, *_VarsType|None run_variables=None, Context|None context=None)
None add_template_attribute(self, str attribute, Template template, Callable[[Any], Any]|None validator=None, Callable[[Any], None]|None on_update=None, bool none_on_template_error=False)
set[ColorMode] filter_supported_color_modes(Iterable[ColorMode] color_modes)
Definition: __init__.py:122
def _async_create_entities(hass, config)
Definition: light.py:124
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: light.py:149
dict[str, Any] rewrite_common_legacy_to_modern_conf(HomeAssistant hass, dict[str, Any] entity_cfg, dict[str, str]|None extra_legacy_fields=None)
str async_generate_entity_id(str entity_id_format, str|None name, Iterable[str]|None current_ids=None, HomeAssistant|None hass=None)
Definition: entity.py:119