1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-20 18:08:00 +00:00
Files

831 lines
28 KiB
Python

"""Support for Template lights."""
from __future__ import annotations
from collections.abc import Callable
import contextlib
import logging
from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_HS_COLOR,
ATTR_RGB_COLOR,
ATTR_RGBW_COLOR,
ATTR_RGBWW_COLOR,
ATTR_TRANSITION,
DEFAULT_MAX_KELVIN,
DEFAULT_MIN_KELVIN,
DOMAIN as LIGHT_DOMAIN,
ENTITY_ID_FORMAT,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
LightEntityFeature,
filter_supported_color_modes,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_EFFECT,
CONF_ENTITY_ID,
CONF_FRIENDLY_NAME,
CONF_LIGHTS,
CONF_NAME,
CONF_RGB,
CONF_STATE,
CONF_UNIQUE_ID,
CONF_VALUE_TEMPLATE,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import color as color_util
from . import TriggerUpdateCoordinator, validators as template_validators
from .const import DOMAIN
from .entity import AbstractTemplateEntity
from .helpers import (
async_setup_template_entry,
async_setup_template_platform,
async_setup_template_preview,
)
from .schemas import (
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA,
make_template_entity_common_modern_schema,
)
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
_LOGGER = logging.getLogger(__name__)
# Legacy
ATTR_COLOR_TEMP = "color_temp"
CONF_COLOR_ACTION = "set_color"
CONF_COLOR_TEMPLATE = "color_template"
CONF_HS = "hs"
CONF_HS_ACTION = "set_hs"
CONF_HS_TEMPLATE = "hs_template"
CONF_RGB_ACTION = "set_rgb"
CONF_RGB_TEMPLATE = "rgb_template"
CONF_RGBW = "rgbw"
CONF_RGBW_ACTION = "set_rgbw"
CONF_RGBW_TEMPLATE = "rgbw_template"
CONF_RGBWW = "rgbww"
CONF_RGBWW_ACTION = "set_rgbww"
CONF_RGBWW_TEMPLATE = "rgbww_template"
CONF_EFFECT_ACTION = "set_effect"
CONF_EFFECT_LIST = "effect_list"
CONF_EFFECT_LIST_TEMPLATE = "effect_list_template"
CONF_EFFECT_TEMPLATE = "effect_template"
CONF_LEVEL = "level"
CONF_LEVEL_ACTION = "set_level"
CONF_LEVEL_TEMPLATE = "level_template"
CONF_MAX_MIREDS = "max_mireds"
CONF_MAX_MIREDS_TEMPLATE = "max_mireds_template"
CONF_MIN_MIREDS = "min_mireds"
CONF_MIN_MIREDS_TEMPLATE = "min_mireds_template"
CONF_OFF_ACTION = "turn_off"
CONF_ON_ACTION = "turn_on"
CONF_SUPPORTS_TRANSITION = "supports_transition"
CONF_SUPPORTS_TRANSITION_TEMPLATE = "supports_transition_template"
CONF_TEMPERATURE_ACTION = "set_temperature"
CONF_TEMPERATURE = "temperature"
CONF_TEMPERATURE_TEMPLATE = "temperature_template"
CONF_WHITE_VALUE_ACTION = "set_white_value"
CONF_WHITE_VALUE = "white_value"
CONF_WHITE_VALUE_TEMPLATE = "white_value_template"
DEFAULT_MIN_MIREDS = 153
DEFAULT_MAX_MIREDS = 500
LEGACY_FIELDS = {
CONF_COLOR_ACTION: CONF_HS_ACTION,
CONF_COLOR_TEMPLATE: CONF_HS,
CONF_EFFECT_LIST_TEMPLATE: CONF_EFFECT_LIST,
CONF_EFFECT_TEMPLATE: CONF_EFFECT,
CONF_HS_TEMPLATE: CONF_HS,
CONF_LEVEL_TEMPLATE: CONF_LEVEL,
CONF_MAX_MIREDS_TEMPLATE: CONF_MAX_MIREDS,
CONF_MIN_MIREDS_TEMPLATE: CONF_MIN_MIREDS,
CONF_RGB_TEMPLATE: CONF_RGB,
CONF_RGBW_TEMPLATE: CONF_RGBW,
CONF_RGBWW_TEMPLATE: CONF_RGBWW,
CONF_SUPPORTS_TRANSITION_TEMPLATE: CONF_SUPPORTS_TRANSITION,
CONF_TEMPERATURE_TEMPLATE: CONF_TEMPERATURE,
CONF_VALUE_TEMPLATE: CONF_STATE,
CONF_WHITE_VALUE_TEMPLATE: CONF_WHITE_VALUE,
}
DEFAULT_NAME = "Template Light"
LIGHT_COMMON_SCHEMA = vol.Schema(
{
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template,
vol.Inclusive(CONF_EFFECT, "effect"): cv.template,
vol.Optional(CONF_HS_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_HS): cv.template,
vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_LEVEL): cv.template,
vol.Optional(CONF_MAX_MIREDS): cv.template,
vol.Optional(CONF_MIN_MIREDS): cv.template,
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGB): cv.template,
vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGBW): cv.template,
vol.Optional(CONF_RGBWW_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGBWW): cv.template,
vol.Optional(CONF_STATE): cv.template,
vol.Optional(CONF_SUPPORTS_TRANSITION): cv.template,
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_TEMPERATURE): cv.template,
}
)
LIGHT_YAML_SCHEMA = LIGHT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_OPTIMISTIC_SCHEMA
).extend(make_template_entity_common_modern_schema(LIGHT_DOMAIN, DEFAULT_NAME).schema)
LIGHT_LEGACY_YAML_SCHEMA = vol.All(
cv.deprecated(CONF_ENTITY_ID),
vol.Schema(
{
vol.Exclusive(CONF_COLOR_ACTION, "hs_legacy_action"): cv.SCRIPT_SCHEMA,
vol.Exclusive(CONF_COLOR_TEMPLATE, "hs_legacy_template"): cv.template,
vol.Exclusive(CONF_HS_ACTION, "hs_legacy_action"): cv.SCRIPT_SCHEMA,
vol.Exclusive(CONF_HS_TEMPLATE, "hs_legacy_template"): cv.template,
vol.Optional(CONF_RGB_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGB_TEMPLATE): cv.template,
vol.Optional(CONF_RGBW_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGBW_TEMPLATE): cv.template,
vol.Optional(CONF_RGBWW_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_RGBWW_TEMPLATE): cv.template,
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
vol.Inclusive(CONF_EFFECT_LIST_TEMPLATE, "effect"): cv.template,
vol.Inclusive(CONF_EFFECT_TEMPLATE, "effect"): cv.template,
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_LEVEL_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_LEVEL_TEMPLATE): cv.template,
vol.Optional(CONF_MAX_MIREDS_TEMPLATE): cv.template,
vol.Optional(CONF_MIN_MIREDS_TEMPLATE): cv.template,
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_SUPPORTS_TRANSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_TEMPERATURE_TEMPLATE): cv.template,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}
).extend(TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY.schema),
)
PLATFORM_SCHEMA = vol.All(
# CONF_WHITE_VALUE_* is deprecated, support will be removed in release 2022.9
cv.removed(CONF_WHITE_VALUE_ACTION),
cv.removed(CONF_WHITE_VALUE_TEMPLATE),
LIGHT_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_LEGACY_YAML_SCHEMA)}
),
)
LIGHT_CONFIG_ENTRY_SCHEMA = LIGHT_COMMON_SCHEMA.extend(
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the template lights."""
await async_setup_template_platform(
hass,
LIGHT_DOMAIN,
config,
StateLightEntity,
TriggerLightEntity,
async_add_entities,
discovery_info,
LEGACY_FIELDS,
legacy_key=CONF_LIGHTS,
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Initialize config entry."""
await async_setup_template_entry(
hass,
config_entry,
async_add_entities,
StateLightEntity,
LIGHT_CONFIG_ENTRY_SCHEMA,
True,
)
@callback
def async_create_preview_light(
hass: HomeAssistant, name: str, config: dict[str, Any]
) -> StateLightEntity:
"""Create a preview."""
return async_setup_template_preview(
hass,
name,
config,
StateLightEntity,
LIGHT_CONFIG_ENTRY_SCHEMA,
True,
)
def _string_to_list(result: str) -> list[float]:
for char in "()[] ":
result = result.replace(char, "")
return [float(v) for v in result.split(",")]
def hs_color_list(entity: AbstractTemplateLight) -> Callable[[Any], list[int] | None]:
"""Convert the result to a list of numbers that represent hue and saturation."""
def convert(result: Any) -> list[int] | None:
if template_validators.check_result_for_none(result):
return None
if isinstance(result, str):
with contextlib.suppress(ValueError):
result = _string_to_list(result)
if (
isinstance(result, (list, tuple))
and len(result) == 2
and all(isinstance(value, (int, float)) for value in result)
):
hue, saturation = result
if not (0 <= hue <= 360) or not (0 <= saturation <= 100):
template_validators.log_validation_result_error(
entity,
CONF_HS,
result,
(
"expected a hue value between 0 and 360 and "
"a saturation value between 0 and 100: (0-360, 0-100)"
),
)
return None
return list(result)
template_validators.log_validation_result_error(
entity,
CONF_HS,
result,
"expected a list of numbers: (0-360, 0-100)",
)
return None
return convert
def rgb_color_list(
entity: AbstractTemplateLight, attribute: str, length: int
) -> Callable[[Any], list[int] | None]:
"""Convert the result to a list of numbers that represent a color."""
example = "[" + ", ".join(("0-255",) * length) + "]"
message = f"expected a list of {length} numbers between 0 and 255: {example}"
def convert(result: Any) -> list[int] | None:
if template_validators.check_result_for_none(result):
return None
if isinstance(result, str):
with contextlib.suppress(ValueError):
result = _string_to_list(result)
if (
isinstance(result, (list, tuple))
and len(result) == length
and all(isinstance(value, (int, float)) for value in result)
):
# Ensure the result are numbers between 0 and 255.
if all(0 <= value <= 255 for value in result):
return list(result)
template_validators.log_validation_result_error(
entity, attribute, result, message
)
return None
return convert
class AbstractTemplateLight(AbstractTemplateEntity, LightEntity):
"""Representation of a template lights features."""
_entity_id_format = ENTITY_ID_FORMAT
_optimistic_entity = True
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
# The super init is not called because TemplateEntity and TriggerEntity will call AbstractTemplateEntity.__init__.
# This ensures that the __init__ on AbstractTemplateEntity is not called twice.
def __init__( # pylint: disable=super-init-not-called
self, name: str, config: dict[str, Any]
) -> None:
"""Initialize the features."""
# Setup state and brightness
self.setup_state_template(
CONF_STATE, "_attr_is_on", template_validators.boolean(self, CONF_STATE)
)
self.setup_template(
CONF_LEVEL,
"_attr_brightness",
template_validators.number(self, CONF_LEVEL, 0, 255, int),
)
# Setup Color temperature
self.setup_template(
CONF_TEMPERATURE,
"_attr_color_temp_kelvin",
self._validate_temperature,
self._update_color("_attr_color_temp_kelvin", ColorMode.COLOR_TEMP),
)
# Setup Hue Saturation
self.setup_template(
CONF_HS,
"_attr_hs_color",
hs_color_list(self),
self._update_color("_attr_hs_color", ColorMode.HS),
render_complex=True,
)
# Setup RGB Colors
for option, attribute, length, colormode in (
(CONF_RGB, "_attr_rgb_color", 3, ColorMode.RGB),
(CONF_RGBW, "_attr_rgbw_color", 4, ColorMode.RGBW),
(CONF_RGBWW, "_attr_rgbww_color", 5, ColorMode.RGBWW),
):
self.setup_template(
option,
attribute,
rgb_color_list(self, option, length),
self._update_color(attribute, colormode),
render_complex=True,
)
# Setup Effect templates
self.setup_template(
CONF_EFFECT_LIST,
"_attr_effect_list",
template_validators.list_of_strings(
self, CONF_EFFECT_LIST, none_on_empty=True
),
render_complex=True,
)
self.setup_template(
CONF_EFFECT,
"_attr_effect",
template_validators.item_in_list(
self, "_attr_effect", "_attr_effect_list", CONF_EFFECT_LIST
),
)
# Min/Max temperature templates
self.setup_template(
CONF_MAX_MIREDS,
"_attr_max_color_temp_kelvin",
template_validators.number(self, CONF_MAX_MIREDS),
self._update_max_mireds,
)
self.setup_template(
CONF_MIN_MIREDS,
"_attr_min_color_temp_kelvin",
template_validators.number(self, CONF_MIN_MIREDS),
self._update_min_mireds,
)
# Transition
self.setup_template(
CONF_SUPPORTS_TRANSITION,
"_supports_transition_template",
template_validators.boolean(self, CONF_SUPPORTS_TRANSITION),
self._update_supports_transition,
)
# Stored values for template attributes
self._supports_transition = False
color_modes = {ColorMode.ONOFF}
for action_id, color_mode in (
(CONF_ON_ACTION, None),
(CONF_OFF_ACTION, None),
(CONF_EFFECT_ACTION, None),
(CONF_TEMPERATURE_ACTION, ColorMode.COLOR_TEMP),
(CONF_LEVEL_ACTION, ColorMode.BRIGHTNESS),
(CONF_HS_ACTION, ColorMode.HS),
(CONF_RGB_ACTION, ColorMode.RGB),
(CONF_RGBW_ACTION, ColorMode.RGBW),
(CONF_RGBWW_ACTION, ColorMode.RGBWW),
):
if (action_config := config.get(action_id)) is not None:
self.add_script(action_id, action_config, name, DOMAIN)
if color_mode:
color_modes.add(color_mode)
self._attr_supported_color_modes = filter_supported_color_modes(color_modes)
if len(self._attr_supported_color_modes) > 1:
self._attr_color_mode = ColorMode.UNKNOWN
if len(self._attr_supported_color_modes) == 1:
self._attr_color_mode = next(iter(self._attr_supported_color_modes))
self._attr_supported_features = LightEntityFeature(0)
if self._action_scripts.get(CONF_EFFECT_ACTION):
self._attr_supported_features |= LightEntityFeature.EFFECT
if self._supports_transition is True:
self._attr_supported_features |= LightEntityFeature.TRANSITION
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
optimistic_set = self.set_optimistic_attributes(**kwargs)
script_id, script_params = self.get_registered_script(**kwargs)
await self.async_run_script(
self._action_scripts[script_id],
run_variables=script_params,
context=self._context,
)
if optimistic_set:
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
off_script = self._action_scripts[CONF_OFF_ACTION]
if ATTR_TRANSITION in kwargs and self._supports_transition is True:
await self.async_run_script(
off_script,
run_variables={"transition": kwargs[ATTR_TRANSITION]},
context=self._context,
)
else:
await self.async_run_script(off_script, context=self._context)
if self._attr_assumed_state:
self._attr_is_on = False
self.async_write_ha_state()
def set_optimistic_attributes(self, **kwargs) -> bool:
"""Update attributes which should be set optimistically.
Returns True if any attribute was updated.
"""
optimistic_set = False
if self._attr_assumed_state:
self._attr_is_on = True
optimistic_set = True
if CONF_LEVEL not in self._templates and ATTR_BRIGHTNESS in kwargs:
_LOGGER.debug(
"Optimistically setting brightness to %s", kwargs[ATTR_BRIGHTNESS]
)
self._attr_brightness = kwargs[ATTR_BRIGHTNESS]
optimistic_set = True
if CONF_TEMPERATURE not in self._templates and ATTR_COLOR_TEMP_KELVIN in kwargs:
self._set_optimistic_color(
"color temperature",
"_attr_color_temp_kelvin",
kwargs[ATTR_COLOR_TEMP_KELVIN],
ColorMode.COLOR_TEMP,
)
optimistic_set = True
if CONF_TEMPERATURE not in self._templates and ATTR_COLOR_TEMP in kwargs:
self._set_optimistic_color(
"color temperature",
"_attr_color_temp_kelvin",
color_util.color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]),
ColorMode.COLOR_TEMP,
)
optimistic_set = True
if CONF_HS not in self._templates and ATTR_HS_COLOR in kwargs:
self._set_optimistic_color(
"hs color", "_attr_hs_color", kwargs[ATTR_HS_COLOR], ColorMode.HS
)
optimistic_set = True
if CONF_RGB not in self._templates and ATTR_RGB_COLOR in kwargs:
self._set_optimistic_color(
"rgb color", "_attr_rgb_color", kwargs[ATTR_RGB_COLOR], ColorMode.RGB
)
optimistic_set = True
if CONF_RGBW not in self._templates and ATTR_RGBW_COLOR in kwargs:
self._set_optimistic_color(
"rgbw color",
"_attr_rgbw_color",
kwargs[ATTR_RGBW_COLOR],
ColorMode.RGBW,
)
optimistic_set = True
if CONF_RGBWW not in self._templates and ATTR_RGBWW_COLOR in kwargs:
self._set_optimistic_color(
"rgbww color",
"_attr_rgbww_color",
kwargs[ATTR_RGBWW_COLOR],
ColorMode.RGBWW,
)
optimistic_set = True
if optimistic_set and not self._attr_assumed_state:
# If we are optmistically setting color or level but the state template
# has not rendered, optimisically set the state to 'on'.
self._attr_is_on = True
return optimistic_set
def _set_optimistic_color(
self, description: str, attribute: str, value: Any, color_mode: ColorMode
) -> None:
_LOGGER.debug(
"Optimistically setting %s to %s",
description,
value,
)
self._attr_color_mode = color_mode
setattr(self, attribute, value)
for option, attr in (
(CONF_TEMPERATURE, "_attr_color_temp_kelvin"),
(CONF_HS, "_attr_hs_color"),
(CONF_RGB, "_attr_rgb_color"),
(CONF_RGBW, "_attr_rgbw_color"),
(CONF_RGBWW, "_attr_rgbww_color"),
):
if attribute == attr:
continue
if option not in self._templates:
setattr(self, attr, None)
def get_registered_script(self, **kwargs) -> tuple[str, dict]:
"""Get registered script for turn_on."""
common_params = {}
if ATTR_BRIGHTNESS in kwargs:
common_params["brightness"] = kwargs[ATTR_BRIGHTNESS]
if ATTR_TRANSITION in kwargs and self._supports_transition is True:
common_params["transition"] = kwargs[ATTR_TRANSITION]
if (
ATTR_COLOR_TEMP_KELVIN in kwargs
and (script := CONF_TEMPERATURE_ACTION) in self._action_scripts
):
kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
common_params[ATTR_COLOR_TEMP_KELVIN] = kelvin
common_params[ATTR_COLOR_TEMP] = (
color_util.color_temperature_kelvin_to_mired(kelvin)
)
return (script, common_params)
if (
ATTR_EFFECT in kwargs
and (script := CONF_EFFECT_ACTION) in self._action_scripts
):
assert self._attr_effect_list is not None
effect = kwargs[ATTR_EFFECT]
if (
self._attr_effect_list is not None
and effect not in self._attr_effect_list
):
_LOGGER.error(
"Received invalid effect: %s for entity %s. Expected one of: %s",
effect,
self.entity_id,
self._attr_effect_list,
)
common_params["effect"] = effect
return (script, common_params)
if (
ATTR_HS_COLOR in kwargs
and (script := CONF_HS_ACTION) in self._action_scripts
):
hs_value = kwargs[ATTR_HS_COLOR]
common_params["hs"] = hs_value
common_params["h"] = int(hs_value[0])
common_params["s"] = int(hs_value[1])
return (script, common_params)
if (
ATTR_RGBWW_COLOR in kwargs
and (script := CONF_RGBWW_ACTION) in self._action_scripts
):
rgbww_value = kwargs[ATTR_RGBWW_COLOR]
common_params["rgbww"] = rgbww_value
common_params["rgb"] = (
int(rgbww_value[0]),
int(rgbww_value[1]),
int(rgbww_value[2]),
)
common_params["r"] = int(rgbww_value[0])
common_params["g"] = int(rgbww_value[1])
common_params["b"] = int(rgbww_value[2])
common_params["cw"] = int(rgbww_value[3])
common_params["ww"] = int(rgbww_value[4])
return (script, common_params)
if (
ATTR_RGBW_COLOR in kwargs
and (script := CONF_RGBW_ACTION) in self._action_scripts
):
rgbw_value = kwargs[ATTR_RGBW_COLOR]
common_params["rgbw"] = rgbw_value
common_params["rgb"] = (
int(rgbw_value[0]),
int(rgbw_value[1]),
int(rgbw_value[2]),
)
common_params["r"] = int(rgbw_value[0])
common_params["g"] = int(rgbw_value[1])
common_params["b"] = int(rgbw_value[2])
common_params["w"] = int(rgbw_value[3])
return (script, common_params)
if (
ATTR_RGB_COLOR in kwargs
and (script := CONF_RGB_ACTION) in self._action_scripts
):
rgb_value = kwargs[ATTR_RGB_COLOR]
common_params["rgb"] = rgb_value
common_params["r"] = int(rgb_value[0])
common_params["g"] = int(rgb_value[1])
common_params["b"] = int(rgb_value[2])
return (script, common_params)
if (
ATTR_BRIGHTNESS in kwargs
and (script := CONF_LEVEL_ACTION) in self._action_scripts
):
return (script, common_params)
return (CONF_ON_ACTION, common_params)
def _update_color(
self, attribute: str, color_mode: ColorMode
) -> Callable[[Any], None]:
"""Update the color."""
def update(render) -> None:
if render is None:
setattr(self, attribute, None)
return
setattr(self, attribute, render)
self._attr_color_mode = color_mode
return update
@callback
def _validate_temperature(self, result: Any) -> int | None:
"""Validate the temperature from the template."""
if template_validators.check_result_for_none(result):
return None
if (min_kelvin := self._attr_min_color_temp_kelvin) is not None:
max_mireds = color_util.color_temperature_kelvin_to_mired(min_kelvin)
else:
max_mireds = DEFAULT_MAX_MIREDS
if (max_kelvin := self._attr_max_color_temp_kelvin) is not None:
min_mireds = color_util.color_temperature_kelvin_to_mired(max_kelvin)
else:
min_mireds = DEFAULT_MIN_MIREDS
if isinstance(result, (int, float)) and min_mireds <= result <= max_mireds:
return color_util.color_temperature_mired_to_kelvin(result)
template_validators.log_validation_result_error(
self,
CONF_TEMPERATURE,
result,
f"expected a number between {min_mireds} and {max_mireds}",
)
return None
@callback
def _update_max_mireds(self, render):
"""Update the max mireds from the template."""
if render is None:
self._attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
return
try:
self._attr_min_color_temp_kelvin = (
color_util.color_temperature_mired_to_kelvin(int(render))
)
except ValueError:
_LOGGER.exception(
"Template must supply an integer temperature within the range for"
" this light, or 'None'"
)
self._attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
@callback
def _update_min_mireds(self, render):
"""Update the min mireds from the template."""
if render is None:
self._attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
return
try:
self._attr_max_color_temp_kelvin = (
color_util.color_temperature_mired_to_kelvin(int(render))
)
except ValueError:
_LOGGER.exception(
"Template must supply an integer temperature within the range for"
" this light, or 'None'"
)
self._attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
@callback
def _update_supports_transition(self, render):
"""Update the supports transition from the template."""
if render is None:
self._supports_transition = False
return
self._attr_supported_features &= ~LightEntityFeature.TRANSITION
self._supports_transition = bool(render)
if self._supports_transition:
self._attr_supported_features |= LightEntityFeature.TRANSITION
class StateLightEntity(TemplateEntity, AbstractTemplateLight):
"""Representation of a templated Light, including dimmable."""
_attr_should_poll = False
def __init__(
self,
hass: HomeAssistant,
config: dict[str, Any],
unique_id: str | None,
) -> None:
"""Initialize the light."""
TemplateEntity.__init__(self, hass, config, unique_id)
name = self._attr_name
if TYPE_CHECKING:
assert name is not None
AbstractTemplateLight.__init__(self, name, config)
class TriggerLightEntity(TriggerEntity, AbstractTemplateLight):
"""Light entity based on trigger data."""
domain = LIGHT_DOMAIN
def __init__(
self,
hass: HomeAssistant,
coordinator: TriggerUpdateCoordinator,
config: ConfigType,
) -> None:
"""Initialize the entity."""
TriggerEntity.__init__(self, hass, coordinator, config)
# Render the _attr_name before initializing TemplateLightEntity
self._attr_name = name = self._rendered.get(CONF_NAME, DEFAULT_NAME)
AbstractTemplateLight.__init__(self, name, config)