1 """Provides functionality to interact with lights."""
3 from __future__
import annotations
5 from collections.abc
import Iterable
8 from datetime
import timedelta
9 from enum
import IntFlag, StrEnum
12 from typing
import Any, Self, cast, final
14 from propcache
import cached_property
15 import voluptuous
as vol
35 DATA_COMPONENT: HassKey[EntityComponent[LightEntity]] =
HassKey(DOMAIN)
36 ENTITY_ID_FORMAT = DOMAIN +
".{}"
37 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
38 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
41 DATA_PROFILES: HassKey[Profiles] =
HassKey(f
"{DOMAIN}_profiles")
45 """Supported features of the light entity."""
54 SUPPORT_BRIGHTNESS = 1
55 SUPPORT_COLOR_TEMP = 2
59 SUPPORT_TRANSITION = 32
62 ATTR_COLOR_MODE =
"color_mode"
64 ATTR_SUPPORTED_COLOR_MODES =
"supported_color_modes"
68 """Possible light color modes."""
71 """Ambiguous color mode"""
73 """Must be the only supported mode"""
74 BRIGHTNESS =
"brightness"
75 """Must be the only supported mode"""
76 COLOR_TEMP =
"color_temp"
83 """Must *NOT* be the only supported mode"""
88 COLOR_MODE_UNKNOWN =
"unknown"
89 COLOR_MODE_ONOFF =
"onoff"
90 COLOR_MODE_BRIGHTNESS =
"brightness"
91 COLOR_MODE_COLOR_TEMP =
"color_temp"
94 COLOR_MODE_RGB =
"rgb"
95 COLOR_MODE_RGBW =
"rgbw"
96 COLOR_MODE_RGBWW =
"rgbww"
97 COLOR_MODE_WHITE =
"white"
101 ColorMode.BRIGHTNESS,
102 ColorMode.COLOR_TEMP,
110 COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {ColorMode.ONOFF}
111 COLOR_MODES_COLOR = {
123 """Filter the given color modes."""
124 color_modes = set(color_modes)
127 or ColorMode.UNKNOWN
in color_modes
128 or (ColorMode.WHITE
in color_modes
and not color_supported(color_modes))
130 raise HomeAssistantError
132 if ColorMode.ONOFF
in color_modes
and len(color_modes) > 1:
133 color_modes.remove(ColorMode.ONOFF)
134 if ColorMode.BRIGHTNESS
in color_modes
and len(color_modes) > 1:
135 color_modes.remove(ColorMode.BRIGHTNESS)
140 color_modes: Iterable[ColorMode | str],
141 ) -> set[ColorMode | str]:
142 """Validate the given color modes."""
143 color_modes = set(color_modes)
146 or ColorMode.UNKNOWN
in color_modes
147 or (ColorMode.BRIGHTNESS
in color_modes
and len(color_modes) > 1)
148 or (ColorMode.ONOFF
in color_modes
and len(color_modes) > 1)
149 or (ColorMode.WHITE
in color_modes
and not color_supported(color_modes))
151 raise vol.Error(f
"Invalid supported_color_modes {sorted(color_modes)}")
156 """Test if brightness is supported."""
159 return not COLOR_MODES_BRIGHTNESS.isdisjoint(color_modes)
163 """Test if color is supported."""
166 return not COLOR_MODES_COLOR.isdisjoint(color_modes)
170 """Test if color temperature is supported."""
173 return ColorMode.COLOR_TEMP
in color_modes
177 """Get supported color modes for a light entity.
179 First try the statemachine, then entity registry.
180 This is the equivalent of entity helper get_supported_features.
182 if state := hass.states.get(entity_id):
183 return state.attributes.get(ATTR_SUPPORTED_COLOR_MODES)
185 entity_registry = er.async_get(hass)
186 if not (entry := entity_registry.async_get(entity_id)):
188 if not entry.capabilities:
191 return entry.capabilities.get(ATTR_SUPPORTED_COLOR_MODES)
195 ATTR_TRANSITION =
"transition"
198 ATTR_RGB_COLOR =
"rgb_color"
199 ATTR_RGBW_COLOR =
"rgbw_color"
200 ATTR_RGBWW_COLOR =
"rgbww_color"
201 ATTR_XY_COLOR =
"xy_color"
202 ATTR_HS_COLOR =
"hs_color"
203 ATTR_COLOR_TEMP =
"color_temp"
204 ATTR_KELVIN =
"kelvin"
205 ATTR_MIN_MIREDS =
"min_mireds"
206 ATTR_MAX_MIREDS =
"max_mireds"
207 ATTR_COLOR_TEMP_KELVIN =
"color_temp_kelvin"
208 ATTR_MIN_COLOR_TEMP_KELVIN =
"min_color_temp_kelvin"
209 ATTR_MAX_COLOR_TEMP_KELVIN =
"max_color_temp_kelvin"
210 ATTR_COLOR_NAME =
"color_name"
214 ATTR_BRIGHTNESS =
"brightness"
215 ATTR_BRIGHTNESS_PCT =
"brightness_pct"
216 ATTR_BRIGHTNESS_STEP =
"brightness_step"
217 ATTR_BRIGHTNESS_STEP_PCT =
"brightness_step_pct"
220 ATTR_PROFILE =
"profile"
224 FLASH_SHORT =
"short"
228 ATTR_EFFECT_LIST =
"effect_list"
231 ATTR_EFFECT =
"effect"
232 EFFECT_COLORLOOP =
"colorloop"
234 EFFECT_RANDOM =
"random"
235 EFFECT_WHITE =
"white"
237 COLOR_GROUP =
"Color descriptors"
239 LIGHT_PROFILES_FILE =
"light_profiles.csv"
242 VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))
243 VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
244 VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100))
245 VALID_BRIGHTNESS_STEP = vol.All(vol.Coerce(int), vol.Clamp(min=-255, max=255))
246 VALID_BRIGHTNESS_STEP_PCT = vol.All(vol.Coerce(float), vol.Clamp(min=-100, max=100))
247 VALID_FLASH = vol.In([FLASH_SHORT, FLASH_LONG])
249 LIGHT_TURN_ON_SCHEMA: VolDictType = {
250 vol.Exclusive(ATTR_PROFILE, COLOR_GROUP): cv.string,
251 ATTR_TRANSITION: VALID_TRANSITION,
252 vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS,
253 vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT,
254 vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP,
255 vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT,
256 vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
257 vol.Exclusive(ATTR_COLOR_TEMP, COLOR_GROUP): vol.All(
258 vol.Coerce(int), vol.Range(min=1)
260 vol.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int,
261 vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): cv.positive_int,
262 vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All(
266 vol.All(vol.Coerce(float), vol.Range(min=0, max=360)),
267 vol.All(vol.Coerce(float), vol.Range(min=0, max=100)),
271 vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(
272 vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 3)
274 vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All(
275 vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 4)
277 vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All(
278 vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 5)
280 vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(
281 vol.Coerce(tuple), vol.ExactSequence((cv.small_float, cv.small_float))
283 vol.Exclusive(ATTR_WHITE, COLOR_GROUP): vol.Any(
True, VALID_BRIGHTNESS),
284 ATTR_FLASH: VALID_FLASH,
285 ATTR_EFFECT: cv.string,
288 LIGHT_TURN_OFF_SCHEMA: VolDictType = {
289 ATTR_TRANSITION: VALID_TRANSITION,
290 ATTR_FLASH: VALID_FLASH,
294 _LOGGER = logging.getLogger(__name__)
298 def is_on(hass: HomeAssistant, entity_id: str) -> bool:
299 """Return if the lights are on based on the statemachine."""
300 return hass.states.is_state(entity_id, STATE_ON)
304 hass: HomeAssistant, params: dict[str, Any]
306 """Process extra data for turn light on request.
311 if ATTR_BRIGHTNESS_STEP
in params
or ATTR_BRIGHTNESS_STEP_PCT
in params:
314 if ATTR_PROFILE
in params:
315 hass.data[DATA_PROFILES].apply_profile(params.pop(ATTR_PROFILE), params)
317 if (color_name := params.pop(ATTR_COLOR_NAME,
None))
is not None:
319 params[ATTR_RGB_COLOR] = color_util.color_name_to_rgb(color_name)
321 _LOGGER.warning(
"Got unknown color %s, falling back to white", color_name)
322 params[ATTR_RGB_COLOR] = (255, 255, 255)
324 if (mired := params.pop(ATTR_COLOR_TEMP,
None))
is not None:
325 kelvin = color_util.color_temperature_mired_to_kelvin(mired)
326 params[ATTR_COLOR_TEMP] =
int(mired)
327 params[ATTR_COLOR_TEMP_KELVIN] =
int(kelvin)
329 if (kelvin := params.pop(ATTR_KELVIN,
None))
is not None:
330 mired = color_util.color_temperature_kelvin_to_mired(kelvin)
331 params[ATTR_COLOR_TEMP] =
int(mired)
332 params[ATTR_COLOR_TEMP_KELVIN] =
int(kelvin)
334 if (kelvin := params.pop(ATTR_COLOR_TEMP_KELVIN,
None))
is not None:
335 mired = color_util.color_temperature_kelvin_to_mired(kelvin)
336 params[ATTR_COLOR_TEMP] =
int(mired)
337 params[ATTR_COLOR_TEMP_KELVIN] =
int(kelvin)
339 brightness_pct = params.pop(ATTR_BRIGHTNESS_PCT,
None)
340 if brightness_pct
is not None:
341 params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100)
345 light: LightEntity, params: dict[str, Any]
347 """Filter out params not used in turn off or not supported by the light."""
351 supported_features = light.supported_features_compat
353 if LightEntityFeature.FLASH
not in supported_features:
354 params.pop(ATTR_FLASH,
None)
355 if LightEntityFeature.TRANSITION
not in supported_features:
356 params.pop(ATTR_TRANSITION,
None)
358 return {k: v
for k, v
in params.items()
if k
in (ATTR_TRANSITION, ATTR_FLASH)}
362 """Filter out params not supported by the light."""
363 supported_features = light.supported_features_compat
365 if LightEntityFeature.EFFECT
not in supported_features:
366 params.pop(ATTR_EFFECT,
None)
367 if LightEntityFeature.FLASH
not in supported_features:
368 params.pop(ATTR_FLASH,
None)
369 if LightEntityFeature.TRANSITION
not in supported_features:
370 params.pop(ATTR_TRANSITION,
None)
372 supported_color_modes = (
373 light._light_internal_supported_color_modes
376 params.pop(ATTR_BRIGHTNESS,
None)
377 if ColorMode.COLOR_TEMP
not in supported_color_modes:
378 params.pop(ATTR_COLOR_TEMP,
None)
379 params.pop(ATTR_COLOR_TEMP_KELVIN,
None)
380 if ColorMode.HS
not in supported_color_modes:
381 params.pop(ATTR_HS_COLOR,
None)
382 if ColorMode.RGB
not in supported_color_modes:
383 params.pop(ATTR_RGB_COLOR,
None)
384 if ColorMode.RGBW
not in supported_color_modes:
385 params.pop(ATTR_RGBW_COLOR,
None)
386 if ColorMode.RGBWW
not in supported_color_modes:
387 params.pop(ATTR_RGBWW_COLOR,
None)
388 if ColorMode.WHITE
not in supported_color_modes:
389 params.pop(ATTR_WHITE,
None)
390 if ColorMode.XY
not in supported_color_modes:
391 params.pop(ATTR_XY_COLOR,
None)
396 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
397 """Expose light control via state machine and services."""
398 component = hass.data[DATA_COMPONENT] = EntityComponent[LightEntity](
399 _LOGGER, DOMAIN, hass, SCAN_INTERVAL
401 await component.async_setup(config)
403 profiles = hass.data[DATA_PROFILES] =
Profiles(hass)
406 hass.async_create_task(profiles.async_initialize(), eager_start=
True)
408 def preprocess_data(data: dict[str, Any]) -> VolDictType:
409 """Preprocess the service data."""
410 base: VolDictType = {
411 entity_field: data.pop(entity_field)
412 for entity_field
in cv.ENTITY_SERVICE_FIELDS
413 if entity_field
in data
417 base[
"params"] = data
420 async
def async_handle_light_on_service(
421 light: LightEntity, call: ServiceCall
423 """Handle turning a light on.
425 If brightness is set to 0, this service will turn the light off.
427 params: dict[str, Any] =
dict(call.data[
"params"])
431 ATTR_BRIGHTNESS_STEP
in params
or ATTR_BRIGHTNESS_STEP_PCT
in params
433 brightness = light.brightness
if light.is_on
and light.brightness
else 0
435 if ATTR_BRIGHTNESS_STEP
in params:
436 brightness += params.pop(ATTR_BRIGHTNESS_STEP)
439 brightness += round(params.pop(ATTR_BRIGHTNESS_STEP_PCT) / 100 * 255)
441 params[ATTR_BRIGHTNESS] =
max(0,
min(255, brightness))
445 if (
not params
or not light.is_on)
or (
446 params
and ATTR_TRANSITION
not in params
448 profiles.apply_default(light.entity_id, light.is_on, params)
450 legacy_supported_color_modes = light._light_internal_supported_color_modes
451 supported_color_modes = light.supported_color_modes
454 if ATTR_COLOR_TEMP_KELVIN
in params:
456 supported_color_modes
457 and ColorMode.COLOR_TEMP
not in supported_color_modes
458 and ColorMode.RGBWW
in supported_color_modes
460 params.pop(ATTR_COLOR_TEMP)
461 color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
462 brightness = params.get(ATTR_BRIGHTNESS, light.brightness)
463 params[ATTR_RGBWW_COLOR] = color_util.color_temperature_to_rgbww(
466 light.min_color_temp_kelvin,
467 light.max_color_temp_kelvin,
469 elif ColorMode.COLOR_TEMP
not in legacy_supported_color_modes:
470 params.pop(ATTR_COLOR_TEMP)
471 color_temp = params.pop(ATTR_COLOR_TEMP_KELVIN)
473 params[ATTR_HS_COLOR] = color_util.color_temperature_to_hs(
480 rgb_color: tuple[int, int, int] |
None
481 rgbww_color: tuple[int, int, int, int, int] |
None
482 if not supported_color_modes:
483 if (rgb_color := params.pop(ATTR_RGB_COLOR,
None))
is not None:
484 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
485 elif (xy_color := params.pop(ATTR_XY_COLOR,
None))
is not None:
486 params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
487 elif (rgbw_color := params.pop(ATTR_RGBW_COLOR,
None))
is not None:
488 rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
489 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
490 elif (rgbww_color := params.pop(ATTR_RGBWW_COLOR,
None))
is not None:
492 rgb_color = color_util.color_rgbww_to_rgb(
494 light.min_color_temp_kelvin,
495 light.max_color_temp_kelvin,
497 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
498 elif ATTR_HS_COLOR
in params
and ColorMode.HS
not in supported_color_modes:
499 hs_color = params.pop(ATTR_HS_COLOR)
500 if ColorMode.RGB
in supported_color_modes:
501 params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
502 elif ColorMode.RGBW
in supported_color_modes:
503 rgb_color = color_util.color_hs_to_RGB(*hs_color)
504 params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
505 elif ColorMode.RGBWW
in supported_color_modes:
506 rgb_color = color_util.color_hs_to_RGB(*hs_color)
507 params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
508 *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
510 elif ColorMode.XY
in supported_color_modes:
511 params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
512 elif ColorMode.COLOR_TEMP
in supported_color_modes:
513 xy_color = color_util.color_hs_to_xy(*hs_color)
514 params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
517 params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
518 params[ATTR_COLOR_TEMP_KELVIN]
520 elif ATTR_RGB_COLOR
in params
and ColorMode.RGB
not in supported_color_modes:
521 rgb_color = params.pop(ATTR_RGB_COLOR)
522 assert rgb_color
is not None
523 if ColorMode.RGBW
in supported_color_modes:
524 params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
525 elif ColorMode.RGBWW
in supported_color_modes:
526 params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
528 light.min_color_temp_kelvin,
529 light.max_color_temp_kelvin,
531 elif ColorMode.HS
in supported_color_modes:
532 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
533 elif ColorMode.XY
in supported_color_modes:
534 params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
535 elif ColorMode.COLOR_TEMP
in supported_color_modes:
536 xy_color = color_util.color_RGB_to_xy(*rgb_color)
537 params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
540 params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
541 params[ATTR_COLOR_TEMP_KELVIN]
543 elif ATTR_XY_COLOR
in params
and ColorMode.XY
not in supported_color_modes:
544 xy_color = params.pop(ATTR_XY_COLOR)
545 if ColorMode.HS
in supported_color_modes:
546 params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
547 elif ColorMode.RGB
in supported_color_modes:
548 params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
549 elif ColorMode.RGBW
in supported_color_modes:
550 rgb_color = color_util.color_xy_to_RGB(*xy_color)
551 params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
552 elif ColorMode.RGBWW
in supported_color_modes:
553 rgb_color = color_util.color_xy_to_RGB(*xy_color)
554 params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
555 *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
557 elif ColorMode.COLOR_TEMP
in supported_color_modes:
558 params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
561 params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
562 params[ATTR_COLOR_TEMP_KELVIN]
564 elif ATTR_RGBW_COLOR
in params
and ColorMode.RGBW
not in supported_color_modes:
565 rgbw_color = params.pop(ATTR_RGBW_COLOR)
566 rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
567 if ColorMode.RGB
in supported_color_modes:
568 params[ATTR_RGB_COLOR] = rgb_color
569 elif ColorMode.RGBWW
in supported_color_modes:
570 params[ATTR_RGBWW_COLOR] = color_util.color_rgb_to_rgbww(
571 *rgb_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
573 elif ColorMode.HS
in supported_color_modes:
574 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
575 elif ColorMode.XY
in supported_color_modes:
576 params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
577 elif ColorMode.COLOR_TEMP
in supported_color_modes:
578 xy_color = color_util.color_RGB_to_xy(*rgb_color)
579 params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
582 params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
583 params[ATTR_COLOR_TEMP_KELVIN]
586 ATTR_RGBWW_COLOR
in params
and ColorMode.RGBWW
not in supported_color_modes
588 rgbww_color = params.pop(ATTR_RGBWW_COLOR)
589 assert rgbww_color
is not None
590 rgb_color = color_util.color_rgbww_to_rgb(
591 *rgbww_color, light.min_color_temp_kelvin, light.max_color_temp_kelvin
593 if ColorMode.RGB
in supported_color_modes:
594 params[ATTR_RGB_COLOR] = rgb_color
595 elif ColorMode.RGBW
in supported_color_modes:
596 params[ATTR_RGBW_COLOR] = color_util.color_rgb_to_rgbw(*rgb_color)
597 elif ColorMode.HS
in supported_color_modes:
598 params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
599 elif ColorMode.XY
in supported_color_modes:
600 params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
601 elif ColorMode.COLOR_TEMP
in supported_color_modes:
602 xy_color = color_util.color_RGB_to_xy(*rgb_color)
603 params[ATTR_COLOR_TEMP_KELVIN] = color_util.color_xy_to_temperature(
606 params[ATTR_COLOR_TEMP] = color_util.color_temperature_kelvin_to_mired(
607 params[ATTR_COLOR_TEMP_KELVIN]
613 if params.get(ATTR_WHITE)
is True:
614 params[ATTR_WHITE] = light.brightness
618 supported_color_modes
619 and ATTR_WHITE
in params
620 and ColorMode.WHITE
in supported_color_modes
622 params[ATTR_WHITE] = params.pop(ATTR_BRIGHTNESS, params[ATTR_WHITE])
625 if params.get(ATTR_BRIGHTNESS) == 0
or params.get(ATTR_WHITE) == 0:
626 await async_handle_light_off_service(light, call)
630 async
def async_handle_light_off_service(
631 light: LightEntity, call: ServiceCall
633 """Handle turning off a light."""
634 params =
dict(call.data[
"params"])
636 if ATTR_TRANSITION
not in params:
637 profiles.apply_default(light.entity_id,
True, params)
641 async
def async_handle_toggle_service(
642 light: LightEntity, call: ServiceCall
644 """Handle toggling a light."""
646 await async_handle_light_off_service(light, call)
648 await async_handle_light_on_service(light, call)
652 component.async_register_entity_service(
654 vol.All(cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), preprocess_data),
655 async_handle_light_on_service,
658 component.async_register_entity_service(
660 vol.All(cv.make_entity_service_schema(LIGHT_TURN_OFF_SCHEMA), preprocess_data),
661 async_handle_light_off_service,
664 component.async_register_entity_service(
666 vol.All(cv.make_entity_service_schema(LIGHT_TURN_ON_SCHEMA), preprocess_data),
667 async_handle_toggle_service,
674 """Set up a config entry."""
679 """Unload a config entry."""
684 """Coerce an empty string as None."""
686 if not isinstance(value, str):
687 raise vol.Invalid(
"Expected a string")
690 raise vol.Invalid(
"Not an empty string")
693 @dataclasses.dataclass
695 """Representation of a profile.
697 The light profiles feature is in a frozen development state
698 until otherwise decided in an architecture discussion.
702 color_x: float |
None = dataclasses.field(repr=
False)
703 color_y: float |
None = dataclasses.field(repr=
False)
704 brightness: int |
None
705 transition: int |
None =
None
706 hs_color: tuple[float, float] |
None = dataclasses.field(init=
False)
713 vol.Any(cv.small_float, _coerce_none),
714 vol.Any(cv.small_float, _coerce_none),
715 vol.Any(cv.byte, _coerce_none),
721 vol.Any(cv.small_float, _coerce_none),
722 vol.Any(cv.small_float, _coerce_none),
723 vol.Any(cv.byte, _coerce_none),
724 vol.Any(VALID_TRANSITION, _coerce_none),
731 """Convert xy to hs color."""
732 if None in (self.color_x, self.color_y):
736 self.
hs_colorhs_color = color_util.color_xy_to_hs(
737 cast(float, self.color_x), cast(float, self.color_y)
742 """Create profile from a CSV row tuple."""
743 return cls(*cls.
SCHEMASCHEMA(csv_row))
747 """Representation of available color profiles.
749 The light profiles feature is in a frozen development state
750 until otherwise decided in an architecture discussion.
754 """Initialize profiles."""
756 self.
datadata: dict[str, Profile] = {}
759 """Load built-in profiles and custom profiles."""
761 os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE),
762 self.
hasshass.config.path(LIGHT_PROFILES_FILE),
766 for profile_path
in profile_paths:
767 if not os.path.isfile(profile_path):
769 with open(profile_path, encoding=
"utf8")
as inp:
770 reader = csv.reader(inp)
777 profile = Profile.from_csv_row(rec)
778 profiles[profile.name] = profile
780 except vol.MultipleInvalid
as ex:
782 "Error parsing light profile row '%s' from %s: %s",
791 """Load and cache profiles."""
796 self, entity_id: str, state_on: bool |
None, params: dict[str, Any]
798 """Return the default profile for the given light."""
799 for _entity_id
in (entity_id,
"group.all_lights"):
800 name = f
"{_entity_id}.default"
801 if name
in self.
datadata:
802 if not state_on
or not params:
804 elif self.
datadata[name].transition
is not None:
805 params.setdefault(ATTR_TRANSITION, self.
datadata[name].transition)
809 """Apply a profile."""
810 if (profile := self.
datadata.
get(name))
is None:
824 if profile.hs_color
is not None and not any(
825 color_attribute
in params
for color_attribute
in color_attributes
827 params[ATTR_HS_COLOR] = profile.hs_color
828 if profile.brightness
is not None:
829 params.setdefault(ATTR_BRIGHTNESS, profile.brightness)
830 if profile.transition
is not None:
831 params.setdefault(ATTR_TRANSITION, profile.transition)
835 """A class that describes binary sensor entities."""
838 CACHED_PROPERTIES_WITH_ATTR_ = {
851 "supported_color_modes",
852 "supported_features",
857 """Base class for light entities."""
859 _entity_component_unrecorded_attributes = frozenset(
861 ATTR_SUPPORTED_COLOR_MODES,
865 ATTR_MIN_COLOR_TEMP_KELVIN,
866 ATTR_MAX_COLOR_TEMP_KELVIN,
870 ATTR_COLOR_TEMP_KELVIN,
880 entity_description: LightEntityDescription
881 _attr_brightness: int |
None =
None
882 _attr_color_mode: ColorMode | str |
None =
None
883 _attr_color_temp: int |
None =
None
884 _attr_color_temp_kelvin: int |
None =
None
885 _attr_effect_list: list[str] |
None =
None
886 _attr_effect: str |
None =
None
887 _attr_hs_color: tuple[float, float] |
None =
None
890 _attr_max_color_temp_kelvin: int |
None =
None
891 _attr_min_color_temp_kelvin: int |
None =
None
892 _attr_max_mireds: int = 500
893 _attr_min_mireds: int = 153
894 _attr_rgb_color: tuple[int, int, int] |
None =
None
895 _attr_rgbw_color: tuple[int, int, int, int] |
None =
None
896 _attr_rgbww_color: tuple[int, int, int, int, int] |
None =
None
897 _attr_supported_color_modes: set[ColorMode] | set[str] |
None =
None
899 _attr_xy_color: tuple[float, float] |
None =
None
901 __color_mode_reported =
False
905 """Return the brightness of this light between 0..255."""
906 return self._attr_brightness
910 """Return the color mode of the light."""
911 return self._attr_color_mode
915 """Return the color mode of the light with backwards compatibility."""
916 if (color_mode := self.
color_modecolor_mode)
is None:
924 "%s (%s) does not report a color mode, this will stop working "
925 "in Home Assistant Core 2025.3, please %s"
934 if ColorMode.HS
in supported
and self.
hs_colorhs_color
is not None:
936 if ColorMode.COLOR_TEMP
in supported
and self.
color_temp_kelvincolor_temp_kelvin
is not None:
937 return ColorMode.COLOR_TEMP
938 if ColorMode.BRIGHTNESS
in supported
and self.
brightnessbrightness
is not None:
939 return ColorMode.BRIGHTNESS
940 if ColorMode.ONOFF
in supported:
941 return ColorMode.ONOFF
942 return ColorMode.UNKNOWN
948 """Return the hue and saturation color value [float, float]."""
949 return self._attr_hs_color
953 """Return the xy color value [float, float]."""
954 return self._attr_xy_color
958 """Return the rgb color value [int, int, int]."""
959 return self._attr_rgb_color
963 """Return the rgbw color value [int, int, int, int]."""
964 return self._attr_rgbw_color
968 """Return the rgbw color value [int, int, int, int]."""
973 """Return the rgbww color value [int, int, int, int, int]."""
974 return self._attr_rgbww_color
978 """Return the CT color value in mireds."""
979 return self._attr_color_temp
983 """Return the CT color value in Kelvin."""
984 if self._attr_color_temp_kelvin
is None and (color_temp := self.
color_tempcolor_temp):
985 return color_util.color_temperature_mired_to_kelvin(color_temp)
986 return self._attr_color_temp_kelvin
990 """Return the coldest color_temp that this light supports."""
991 return self._attr_min_mireds
995 """Return the warmest color_temp that this light supports."""
996 return self._attr_max_mireds
1000 """Return the warmest color_temp_kelvin that this light supports."""
1001 if self._attr_min_color_temp_kelvin
is None:
1002 return color_util.color_temperature_mired_to_kelvin(self.
max_miredsmax_mireds)
1003 return self._attr_min_color_temp_kelvin
1007 """Return the coldest color_temp_kelvin that this light supports."""
1008 if self._attr_max_color_temp_kelvin
is None:
1009 return color_util.color_temperature_mired_to_kelvin(self.
min_miredsmin_mireds)
1010 return self._attr_max_color_temp_kelvin
1014 """Return the list of supported effects."""
1015 return self._attr_effect_list
1019 """Return the current effect."""
1020 return self._attr_effect
1024 """Return capability attributes."""
1025 data: dict[str, Any] = {}
1029 if ColorMode.COLOR_TEMP
in supported_color_modes:
1032 data[ATTR_MIN_COLOR_TEMP_KELVIN] = min_color_temp_kelvin
1033 data[ATTR_MAX_COLOR_TEMP_KELVIN] = max_color_temp_kelvin
1034 if not max_color_temp_kelvin:
1035 data[ATTR_MIN_MIREDS] =
None
1037 data[ATTR_MIN_MIREDS] = color_util.color_temperature_kelvin_to_mired(
1038 max_color_temp_kelvin
1040 if not min_color_temp_kelvin:
1041 data[ATTR_MAX_MIREDS] =
None
1043 data[ATTR_MAX_MIREDS] = color_util.color_temperature_kelvin_to_mired(
1044 min_color_temp_kelvin
1046 if LightEntityFeature.EFFECT
in supported_features:
1047 data[ATTR_EFFECT_LIST] = self.
effect_listeffect_list
1049 data[ATTR_SUPPORTED_COLOR_MODES] = sorted(supported_color_modes)
1054 self, color_mode: ColorMode | str
1055 ) -> dict[str, tuple[float, ...]]:
1056 data: dict[str, tuple[float, ...]] = {}
1057 if color_mode == ColorMode.HS
and (hs_color := self.
hs_colorhs_color):
1058 data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3))
1059 data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
1060 data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
1061 elif color_mode == ColorMode.XY
and (xy_color := self.
xy_colorxy_color):
1062 data[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
1063 data[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
1064 data[ATTR_XY_COLOR] = (round(xy_color[0], 6), round(xy_color[1], 6))
1065 elif color_mode == ColorMode.RGB
and (rgb_color := self.
rgb_colorrgb_color):
1066 data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
1067 data[ATTR_RGB_COLOR] =
tuple(
int(x)
for x
in rgb_color[0:3])
1068 data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
1069 elif color_mode == ColorMode.RGBW
and (
1072 rgb_color = color_util.color_rgbw_to_rgb(*rgbw_color)
1073 data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
1074 data[ATTR_RGB_COLOR] =
tuple(
int(x)
for x
in rgb_color[0:3])
1075 data[ATTR_RGBW_COLOR] =
tuple(
int(x)
for x
in rgbw_color[0:4])
1076 data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
1077 elif color_mode == ColorMode.RGBWW
and (rgbww_color := self.
rgbww_colorrgbww_color):
1078 rgb_color = color_util.color_rgbww_to_rgb(
1081 data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
1082 data[ATTR_RGB_COLOR] =
tuple(
int(x)
for x
in rgb_color[0:3])
1083 data[ATTR_RGBWW_COLOR] =
tuple(
int(x)
for x
in rgbww_color[0:5])
1084 data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
1085 elif color_mode == ColorMode.COLOR_TEMP
and (
1088 hs_color = color_util.color_temperature_to_hs(color_temp_kelvin)
1089 data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3))
1090 data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
1091 data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
1096 color_mode: ColorMode | str |
None,
1097 supported_color_modes: set[ColorMode] | set[str],
1100 """Validate the color mode."""
1101 if color_mode
is None or color_mode == ColorMode.UNKNOWN:
1105 if not effect
or effect == EFFECT_OFF:
1108 if color_mode
in supported_color_modes:
1116 "%s (%s) set to unsupported color mode %s, expected one of %s, "
1117 "this will stop working in Home Assistant Core 2025.3, "
1123 supported_color_modes,
1132 effect_color_modes = supported_color_modes | {ColorMode.ONOFF}
1134 effect_color_modes.add(ColorMode.BRIGHTNESS)
1136 if color_mode
in effect_color_modes:
1145 "%s (%s) set to unsupported color mode %s when rendering an effect,"
1146 " expected one of %s, this will stop working in Home Assistant "
1147 "Core 2025.3, please %s"
1159 supported_color_modes: set[ColorMode] | set[str],
1161 """Validate the supported color modes."""
1174 "%s (%s) sets invalid supported color modes %s, this will stop "
1175 "working in Home Assistant Core 2025.3, please %s"
1179 supported_color_modes,
1186 """Return state attributes."""
1187 data: dict[str, Any] = {}
1190 legacy_supported_color_modes = (
1193 supported_features_value = supported_features.value
1194 _is_on = self.
is_onis_on
1198 if LightEntityFeature.EFFECT
in supported_features:
1199 data[ATTR_EFFECT] = effect = self.
effecteffect
if _is_on
else None
1205 data[ATTR_COLOR_MODE] = color_mode
1208 if color_mode
in COLOR_MODES_BRIGHTNESS:
1209 data[ATTR_BRIGHTNESS] = self.
brightnessbrightness
1211 data[ATTR_BRIGHTNESS] =
None
1212 elif supported_features_value & SUPPORT_BRIGHTNESS:
1216 data[ATTR_BRIGHTNESS] = self.
brightnessbrightness
1218 data[ATTR_BRIGHTNESS] =
None
1221 if color_mode == ColorMode.COLOR_TEMP:
1223 data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin
1224 if color_temp_kelvin:
1225 data[ATTR_COLOR_TEMP] = (
1226 color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
1229 data[ATTR_COLOR_TEMP] =
None
1231 data[ATTR_COLOR_TEMP_KELVIN] =
None
1232 data[ATTR_COLOR_TEMP] =
None
1233 elif supported_features_value & SUPPORT_COLOR_TEMP:
1238 data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin
1239 if color_temp_kelvin:
1240 data[ATTR_COLOR_TEMP] = (
1241 color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
1244 data[ATTR_COLOR_TEMP] =
None
1246 data[ATTR_COLOR_TEMP_KELVIN] =
None
1247 data[ATTR_COLOR_TEMP] =
None
1250 legacy_supported_color_modes
1252 data[ATTR_HS_COLOR] =
None
1253 data[ATTR_RGB_COLOR] =
None
1254 data[ATTR_XY_COLOR] =
None
1255 if ColorMode.RGBW
in legacy_supported_color_modes:
1256 data[ATTR_RGBW_COLOR] =
None
1257 if ColorMode.RGBWW
in legacy_supported_color_modes:
1258 data[ATTR_RGBWW_COLOR] =
None
1266 """Calculate supported color modes with backwards compatibility."""
1269 return _supported_color_modes
1278 "%s (%s) does not set supported color modes, this will stop working"
1279 " in Home Assistant Core 2025.3, please %s"
1286 supported_features_value = supported_features.value
1287 supported_color_modes: set[ColorMode] = set()
1289 if supported_features_value & SUPPORT_COLOR_TEMP:
1290 supported_color_modes.add(ColorMode.COLOR_TEMP)
1291 if supported_features_value & SUPPORT_COLOR:
1292 supported_color_modes.add(ColorMode.HS)
1293 if not supported_color_modes
and supported_features_value & SUPPORT_BRIGHTNESS:
1294 supported_color_modes = {ColorMode.BRIGHTNESS}
1296 if not supported_color_modes:
1297 supported_color_modes = {ColorMode.ONOFF}
1299 return supported_color_modes
1303 """Flag supported color modes."""
1304 return self._attr_supported_color_modes
1308 """Flag supported features."""
1309 return self._attr_supported_features
1313 """Return the supported features as LightEntityFeature.
1315 Remove this compatibility shim in 2025.1 or later.
1318 if type(features)
is not int:
1327 "https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
1331 "Entity %s (%s) is using deprecated supported features"
1332 " values which will be removed in HA Core 2025.1. Instead it should use"
1333 " %s and color modes, please %s"
1343 """Return if light color mode issues should be reported."""
1347 return self.
platformplatform.platform_name
not in {
"philips_js"}
bool __should_report_light_issue(self)
list[str]|None effect_list(self)
int|None color_temp_kelvin(self)
tuple[int, int, int, int]|None _light_internal_rgbw_color(self)
int|None brightness(self)
int min_color_temp_kelvin(self)
int max_color_temp_kelvin(self)
tuple[int, int, int]|None rgb_color(self)
dict[str, tuple[float,...]] _light_internal_convert_color(self, ColorMode|str color_mode)
tuple[int, int, int, int]|None rgbw_color(self)
str _light_internal_color_mode(self)
tuple[int, int, int, int, int]|None rgbww_color(self)
set[ColorMode]|set[str] _light_internal_supported_color_modes(self)
int|None color_temp(self)
dict[str, Any] capability_attributes(self)
dict[str, Any]|None state_attributes(self)
set[ColorMode]|set[str]|None supported_color_modes(self)
None __validate_color_mode(self, ColorMode|str|None color_mode, set[ColorMode]|set[str] supported_color_modes, str|None effect)
_deprecated_supported_features_reported
tuple[float, float]|None hs_color(self)
None __validate_supported_color_modes(self, set[ColorMode]|set[str] supported_color_modes)
LightEntityFeature supported_features_compat(self)
ColorMode|str|None color_mode(self)
tuple[float, float]|None xy_color(self)
bool __color_mode_reported
LightEntityFeature supported_features(self)
Self from_csv_row(cls, list[str] csv_row)
None apply_default(self, str entity_id, bool|None state_on, dict[str, Any] params)
None __init__(self, HomeAssistant hass)
None apply_profile(self, str name, dict[str, Any] params)
None async_initialize(self)
dict[str, Profile] _load_profile_data(self)
_deprecated_supported_features_reported
bool _deprecated_supported_features_reported
str _suggest_report_issue(self)
int|None supported_features(self)
web.Response get(self, web.Request request, str config_key)
bool color_supported(Iterable[ColorMode|str]|None color_modes)
bool async_setup(HomeAssistant hass, ConfigType config)
set[ColorMode|str] valid_supported_color_modes(Iterable[ColorMode|str] color_modes)
set[ColorMode] filter_supported_color_modes(Iterable[ColorMode] color_modes)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
dict[str, Any] filter_turn_on_params(LightEntity light, dict[str, Any] params)
None preprocess_turn_on_alternatives(HomeAssistant hass, dict[str, Any] params)
set[str]|None get_supported_color_modes(HomeAssistant hass, str entity_id)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool brightness_supported(Iterable[ColorMode|str]|None color_modes)
dict[str, Any] filter_turn_off_params(LightEntity light, dict[str, Any] params)
bool color_temp_supported(Iterable[ColorMode|str]|None color_modes)
None _coerce_none(str value)
bool is_on(HomeAssistant hass, str entity_id)
None open(self, **Any kwargs)