-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add color to light template #28141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add color to light template #28141
Changes from all commits
4016dd6
44b60cc
cf3423e
f99d575
fe0c527
e16a3d0
d732083
90c1c64
5d5131a
53b08dd
7fac622
0cd16c5
7cb5b3a
527ac8c
6949881
538b45f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,12 @@ | |
|
|
||
| from homeassistant.components.light import ( | ||
| ATTR_BRIGHTNESS, | ||
| ATTR_COLOR_TEMP, | ||
| ATTR_HS_COLOR, | ||
| ENTITY_ID_FORMAT, | ||
| SUPPORT_BRIGHTNESS, | ||
| SUPPORT_COLOR, | ||
| SUPPORT_COLOR_TEMP, | ||
| Light, | ||
| ) | ||
| from homeassistant.const import ( | ||
|
|
@@ -38,6 +42,10 @@ | |
| CONF_OFF_ACTION = "turn_off" | ||
| CONF_LEVEL_ACTION = "set_level" | ||
| CONF_LEVEL_TEMPLATE = "level_template" | ||
| CONF_TEMPERATURE_TEMPLATE = "temperature_template" | ||
| CONF_TEMPERATURE_ACTION = "set_temperature" | ||
| CONF_COLOR_TEMPLATE = "color_template" | ||
| CONF_COLOR_ACTION = "set_color" | ||
|
|
||
| LIGHT_SCHEMA = vol.Schema( | ||
| { | ||
|
|
@@ -51,6 +59,10 @@ | |
| vol.Optional(CONF_LEVEL_TEMPLATE): cv.template, | ||
| vol.Optional(CONF_FRIENDLY_NAME): cv.string, | ||
| vol.Optional(CONF_ENTITY_ID): cv.entity_ids, | ||
| vol.Optional(CONF_TEMPERATURE_TEMPLATE): cv.template, | ||
| vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA, | ||
| vol.Optional(CONF_COLOR_TEMPLATE): cv.template, | ||
| vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA, | ||
| } | ||
| ) | ||
|
|
||
|
|
@@ -70,18 +82,24 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= | |
| icon_template = device_config.get(CONF_ICON_TEMPLATE) | ||
| entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) | ||
| availability_template = device_config.get(CONF_AVAILABILITY_TEMPLATE) | ||
| level_template = device_config.get(CONF_LEVEL_TEMPLATE) | ||
|
|
||
| on_action = device_config[CONF_ON_ACTION] | ||
| off_action = device_config[CONF_OFF_ACTION] | ||
| level_action = device_config.get(CONF_LEVEL_ACTION) | ||
| level_template = device_config.get(CONF_LEVEL_TEMPLATE) | ||
| temperature_action = device_config.get(CONF_TEMPERATURE_ACTION) | ||
| temperature_template = device_config.get(CONF_TEMPERATURE_TEMPLATE) | ||
| color_action = device_config.get(CONF_COLOR_ACTION) | ||
| color_template = device_config.get(CONF_COLOR_TEMPLATE) | ||
|
|
||
| templates = { | ||
| CONF_VALUE_TEMPLATE: state_template, | ||
| CONF_ICON_TEMPLATE: icon_template, | ||
| CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template, | ||
| CONF_AVAILABILITY_TEMPLATE: availability_template, | ||
| CONF_LEVEL_TEMPLATE: level_template, | ||
| CONF_TEMPERATURE_TEMPLATE: temperature_template, | ||
| CONF_COLOR_TEMPLATE: color_template, | ||
| } | ||
|
|
||
| initialise_templates(hass, templates) | ||
|
|
@@ -101,6 +119,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= | |
| level_action, | ||
| level_template, | ||
| entity_ids, | ||
| temperature_action, | ||
| temperature_template, | ||
| color_action, | ||
| color_template, | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -129,6 +151,10 @@ def __init__( | |
| level_action, | ||
| level_template, | ||
| entity_ids, | ||
| temperature_action, | ||
| temperature_template, | ||
| color_action, | ||
| color_template, | ||
| ): | ||
| """Initialize the light.""" | ||
| self.hass = hass | ||
|
|
@@ -146,11 +172,21 @@ def __init__( | |
| if level_action is not None: | ||
| self._level_script = Script(hass, level_action) | ||
| self._level_template = level_template | ||
| self._temperature_script = None | ||
| if temperature_action is not None: | ||
| self._temperature_script = Script(hass, temperature_action) | ||
| self._temperature_template = temperature_template | ||
| self._color_script = None | ||
| if color_action is not None: | ||
| self._color_script = Script(hass, color_action) | ||
| self._color_template = color_template | ||
|
|
||
| self._state = False | ||
| self._icon = None | ||
| self._entity_picture = None | ||
| self._brightness = None | ||
| self._temperature = None | ||
| self._color = None | ||
| self._entities = entity_ids | ||
| self._available = True | ||
|
|
||
|
|
@@ -164,12 +200,26 @@ def __init__( | |
| self._entity_picture_template.hass = self.hass | ||
| if self._availability_template is not None: | ||
| self._availability_template.hass = self.hass | ||
| if self._temperature_template is not None: | ||
| self._temperature_template.hass = self.hass | ||
| if self._color_template is not None: | ||
| self._color_template.hass = self.hass | ||
|
|
||
| @property | ||
| def brightness(self): | ||
| """Return the brightness of the light.""" | ||
| return self._brightness | ||
|
|
||
| @property | ||
| def color_temp(self): | ||
| """Return the CT color value in mireds.""" | ||
| return self._temperature | ||
|
|
||
| @property | ||
| def hs_color(self): | ||
| """Return the hue and saturation color value [float, float].""" | ||
| return self._color | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the display name of this light.""" | ||
|
|
@@ -178,10 +228,14 @@ def name(self): | |
| @property | ||
| def supported_features(self): | ||
| """Flag supported features.""" | ||
| supported_features = 0 | ||
| if self._level_script is not None: | ||
| return SUPPORT_BRIGHTNESS | ||
|
|
||
| return 0 | ||
| supported_features |= SUPPORT_BRIGHTNESS | ||
| if self._temperature_script is not None: | ||
| supported_features |= SUPPORT_COLOR_TEMP | ||
| if self._color_script is not None: | ||
| supported_features |= SUPPORT_COLOR | ||
| return supported_features | ||
|
|
||
| @property | ||
| def is_on(self): | ||
|
|
@@ -222,6 +276,8 @@ def template_light_startup(event): | |
| if ( | ||
| self._template is not None | ||
| or self._level_template is not None | ||
| or self._temperature_template is not None | ||
| or self._color_template is not None | ||
| or self._availability_template is not None | ||
| ): | ||
| async_track_state_change( | ||
|
|
@@ -249,10 +305,33 @@ async def async_turn_on(self, **kwargs): | |
| self._brightness = kwargs[ATTR_BRIGHTNESS] | ||
| optimistic_set = True | ||
|
|
||
| if self._temperature_template is None and ATTR_COLOR_TEMP in kwargs: | ||
| _LOGGER.info( | ||
| "Optimistically setting color temperature to %s", | ||
| kwargs[ATTR_COLOR_TEMP], | ||
| ) | ||
| self._temperature = kwargs[ATTR_COLOR_TEMP] | ||
| optimistic_set = True | ||
|
|
||
| if self._color_template is None and ATTR_HS_COLOR in kwargs: | ||
| _LOGGER.info("Optimistically setting color to %s", kwargs[ATTR_HS_COLOR]) | ||
| self._color = kwargs[ATTR_HS_COLOR] | ||
| optimistic_set = True | ||
|
|
||
| if ATTR_BRIGHTNESS in kwargs and self._level_script: | ||
| await self._level_script.async_run( | ||
| {"brightness": kwargs[ATTR_BRIGHTNESS]}, context=self._context | ||
| ) | ||
| elif ATTR_COLOR_TEMP in kwargs and self._temperature_script: | ||
| await self._temperature_script.async_run( | ||
| {"color_temp": kwargs[ATTR_COLOR_TEMP]}, context=self._context | ||
| ) | ||
| elif ATTR_HS_COLOR in kwargs and self._color_script: | ||
| hs_value = kwargs[ATTR_HS_COLOR] | ||
| await self._color_script.async_run( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are all these different scripts? Since it is all done inside the async_turn_on method, shouldn't there just be a generic turn_on script ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these scripts map the actions to call when the brightness/color/temperature is updated. See https://www.home-assistant.io/integrations/light.template |
||
| {"hs": hs_value, "h": int(hs_value[0]), "s": int(hs_value[1])}, | ||
| context=self._context, | ||
| ) | ||
| else: | ||
| await self._on_script.async_run() | ||
|
|
||
|
|
@@ -267,38 +346,14 @@ async def async_turn_off(self, **kwargs): | |
| self.async_schedule_update_ha_state() | ||
|
|
||
| async def async_update(self): | ||
| """Update the state from the template.""" | ||
| if self._template is not None: | ||
| try: | ||
| state = self._template.async_render().lower() | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None | ||
| """Update from templates.""" | ||
| self.update_state() | ||
|
|
||
| if state in _VALID_STATES: | ||
| self._state = state in ("true", STATE_ON) | ||
| else: | ||
| _LOGGER.error( | ||
| "Received invalid light is_on state: %s. Expected: %s", | ||
| state, | ||
| ", ".join(_VALID_STATES), | ||
| ) | ||
| self._state = None | ||
| self.update_brightness() | ||
|
|
||
| if self._level_template is not None: | ||
| try: | ||
| brightness = self._level_template.async_render() | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None | ||
| self.update_temperature() | ||
|
|
||
| if 0 <= int(brightness) <= 255: | ||
| self._brightness = int(brightness) | ||
| else: | ||
| _LOGGER.error( | ||
| "Received invalid brightness : %s. Expected: 0-255", brightness | ||
| ) | ||
| self._brightness = None | ||
| self.update_color() | ||
|
|
||
| for property_name, template in ( | ||
| ("_icon", self._icon_template), | ||
|
|
@@ -320,7 +375,7 @@ async def async_update(self): | |
| ): | ||
| # Common during HA startup - so just a warning | ||
| _LOGGER.warning( | ||
| "Could not render %s template %s," " the state is unknown.", | ||
| "Could not render %s template %s, the state is unknown.", | ||
| friendly_property_name, | ||
| self._name, | ||
| ) | ||
|
|
@@ -335,3 +390,90 @@ async def async_update(self): | |
| self._name, | ||
| ex, | ||
| ) | ||
|
|
||
| @callback | ||
| def update_temperature(self): | ||
| """Update the temperature from the template.""" | ||
| if self._temperature_template is not None: | ||
| try: | ||
| temperature = int(self._temperature_template.async_render()) | ||
| except TemplateError: | ||
| _LOGGER.error("Cannot evaluate temperature template", exc_info=True) | ||
| self._temperature = None | ||
|
|
||
| if self.min_mireds <= temperature <= self.max_mireds: | ||
| self._temperature = temperature | ||
| else: | ||
| _LOGGER.error( | ||
| "Received invalid color temperature : %s. Expected: 0-%s", | ||
| temperature, | ||
| self.max_mireds, | ||
| ) | ||
| self._temperature = None | ||
|
|
||
| @callback | ||
| def update_brightness(self): | ||
| """Update the brightness from the template.""" | ||
| if self._level_template is not None: | ||
| try: | ||
| brightness = self._level_template.async_render() | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None | ||
|
|
||
| if 0 <= int(brightness) <= 255: | ||
| self._brightness = int(brightness) | ||
| else: | ||
| _LOGGER.error( | ||
| "Received invalid brightness : %s. Expected: 0-255", brightness | ||
| ) | ||
| self._brightness = None | ||
|
|
||
| @callback | ||
| def update_color(self): | ||
| """Update the hs_color from the template.""" | ||
| if self._color_template is not None: | ||
| h_str = None | ||
| s_str = None | ||
| self._color = None | ||
|
|
||
| try: | ||
| render = self._color_template.async_render() | ||
| h_str, s_str = map( | ||
| int, render.replace("(", "").replace(")", "").split(",", 1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be float There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I didn't know. I will change this. |
||
| ) | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| if ( | ||
| h_str is not None | ||
| and s_str is not None | ||
| and 0 <= h_str <= 360 | ||
| and 0 <= s_str <= 100 | ||
| ): | ||
| self._color = (h_str, s_str) | ||
| elif h_str is not None and s_str is not None: | ||
| _LOGGER.error( | ||
| "Received invalid hs_color : (%s, %s). Expected: (0-360, 0-100)", | ||
| h_str, | ||
| s_str, | ||
| ) | ||
|
|
||
| @callback | ||
| def update_state(self): | ||
| """Update the state from the template.""" | ||
| if self._template is not None: | ||
| try: | ||
| state = self._template.async_render().lower() | ||
| except TemplateError as ex: | ||
| _LOGGER.error(ex) | ||
| self._state = None | ||
|
|
||
| if state in _VALID_STATES: | ||
| self._state = state in ("true", STATE_ON) | ||
| else: | ||
| _LOGGER.error( | ||
| "Received invalid light is_on state: %s. Expected: %s", | ||
| state, | ||
| ", ".join(_VALID_STATES), | ||
| ) | ||
| self._state = None | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,6 @@ | |
| "requirements": [], | ||
| "dependencies": [], | ||
| "codeowners": [ | ||
| "@PhracturedBlue" | ||
| "@PhracturedBlue", "@tetienne" | ||
| ] | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting that this is based off the existence of a script, not on a value template ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I just follow the existing solution. But IMO it seems logic: you need an action to be able to change the brightness/color/temperature.