From c5d2e44baf24e5f14dadcc5ce040db99a4e6e7d8 Mon Sep 17 00:00:00 2001 From: Elahd Bar-Shai <elahd@users.noreply.github.com> Date: Mon, 20 Apr 2020 08:26:28 -0400 Subject: [PATCH] Add white value in light template platform (#32481) * Added support for white value in Template Light integration. * Tests now working. * Clean up * Make template more robust Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --- homeassistant/components/template/light.py | 67 ++++++++++++++ tests/components/template/test_light.py | 103 ++++++++++++++++++++- 2 files changed, 168 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index c6d6656f134..1c19dfb33a6 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -7,10 +7,12 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, + ATTR_WHITE_VALUE, ENTITY_ID_FORMAT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + SUPPORT_WHITE_VALUE, Light, ) from homeassistant.const import ( @@ -46,6 +48,8 @@ CONF_TEMPERATURE_TEMPLATE = "temperature_template" CONF_TEMPERATURE_ACTION = "set_temperature" CONF_COLOR_TEMPLATE = "color_template" CONF_COLOR_ACTION = "set_color" +CONF_WHITE_VALUE_TEMPLATE = "white_value_template" +CONF_WHITE_VALUE_ACTION = "set_white_value" LIGHT_SCHEMA = vol.Schema( { @@ -63,6 +67,8 @@ LIGHT_SCHEMA = vol.Schema( vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA, vol.Optional(CONF_COLOR_TEMPLATE): cv.template, vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_WHITE_VALUE_ACTION): cv.SCRIPT_SCHEMA, } ) @@ -95,6 +101,9 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= color_action = device_config.get(CONF_COLOR_ACTION) color_template = device_config.get(CONF_COLOR_TEMPLATE) + white_value_action = device_config.get(CONF_WHITE_VALUE_ACTION) + white_value_template = device_config.get(CONF_WHITE_VALUE_TEMPLATE) + templates = { CONF_VALUE_TEMPLATE: state_template, CONF_ICON_TEMPLATE: icon_template, @@ -103,6 +112,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= CONF_LEVEL_TEMPLATE: level_template, CONF_TEMPERATURE_TEMPLATE: temperature_template, CONF_COLOR_TEMPLATE: color_template, + CONF_WHITE_VALUE_TEMPLATE: white_value_template, } initialise_templates(hass, templates) @@ -128,6 +138,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= temperature_template, color_action, color_template, + white_value_action, + white_value_template, ) ) @@ -155,6 +167,8 @@ class LightTemplate(Light): temperature_template, color_action, color_template, + white_value_action, + white_value_template, ): """Initialize the light.""" self.hass = hass @@ -180,6 +194,10 @@ class LightTemplate(Light): if color_action is not None: self._color_script = Script(hass, color_action) self._color_template = color_template + self._white_value_script = None + if white_value_action is not None: + self._white_value_script = Script(hass, white_value_action) + self._white_value_template = white_value_template self._state = False self._icon = None @@ -187,6 +205,7 @@ class LightTemplate(Light): self._brightness = None self._temperature = None self._color = None + self._white_value = None self._entities = entity_ids self._available = True @@ -200,6 +219,11 @@ class LightTemplate(Light): """Return the CT color value in mireds.""" return self._temperature + @property + def white_value(self): + """Return the white value.""" + return self._white_value + @property def hs_color(self): """Return the hue and saturation color value [float, float].""" @@ -220,6 +244,8 @@ class LightTemplate(Light): supported_features |= SUPPORT_COLOR_TEMP if self._color_script is not None: supported_features |= SUPPORT_COLOR + if self._white_value_script is not None: + supported_features |= SUPPORT_WHITE_VALUE return supported_features @property @@ -263,6 +289,7 @@ class LightTemplate(Light): or self._level_template is not None or self._temperature_template is not None or self._color_template is not None + or self._white_value_template is not None or self._availability_template is not None ): async_track_state_change( @@ -290,6 +317,13 @@ class LightTemplate(Light): self._brightness = kwargs[ATTR_BRIGHTNESS] optimistic_set = True + if self._white_value_template is None and ATTR_WHITE_VALUE in kwargs: + _LOGGER.info( + "Optimistically setting white value to %s", kwargs[ATTR_WHITE_VALUE] + ) + self._white_value = kwargs[ATTR_WHITE_VALUE] + optimistic_set = True + if self._temperature_template is None and ATTR_COLOR_TEMP in kwargs: _LOGGER.info( "Optimistically setting color temperature to %s", @@ -306,6 +340,10 @@ class LightTemplate(Light): await self._temperature_script.async_run( {"color_temp": kwargs[ATTR_COLOR_TEMP]}, context=self._context ) + elif ATTR_WHITE_VALUE in kwargs and self._white_value_script: + await self._white_value_script.async_run( + {"white_value": kwargs[ATTR_WHITE_VALUE]}, 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( @@ -335,6 +373,8 @@ class LightTemplate(Light): self.update_color() + self.update_white_value() + for property_name, template in ( ("_icon", self._icon_template), ("_entity_picture", self._entity_picture_template), @@ -398,6 +438,33 @@ class LightTemplate(Light): _LOGGER.error("Invalid template", exc_info=True) self._brightness = None + @callback + def update_white_value(self): + """Update the white value from the template.""" + if self._white_value_template is None: + return + try: + white_value = self._white_value_template.async_render() + if white_value in ("None", ""): + self._white_value = None + return + if 0 <= int(white_value) <= 255: + self._white_value = int(white_value) + else: + _LOGGER.error( + "Received invalid white value: %s. Expected: 0-255", white_value + ) + self._white_value = None + except ValueError: + _LOGGER.error( + "Template must supply an integer white_value from 0-255, or 'None'", + exc_info=True, + ) + self._white_value = None + except TemplateError as ex: + _LOGGER.error(ex) + self._state = None + @callback def update_state(self): """Update the state from the template.""" diff --git a/tests/components/template/test_light.py b/tests/components/template/test_light.py index d9adf6015c9..9982d72326b 100644 --- a/tests/components/template/test_light.py +++ b/tests/components/template/test_light.py @@ -8,6 +8,7 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, + ATTR_WHITE_VALUE, ) from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE from homeassistant.core import callback @@ -35,7 +36,7 @@ class TestTemplateLight: @callback def record_call(service): - """Track function calls..""" + """Track function calls.""" self.calls.append(service) self.hass.services.register("test", "automation", record_call) @@ -494,6 +495,105 @@ class TestTemplateLight: state = self.hass.states.get("light.test_template_light") assert state.state == STATE_OFF + def test_white_value_action_no_template(self): + """Test setting white value with optimistic template.""" + assert setup.setup_component( + self.hass, + "light", + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{1 == 1}}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_white_value": { + "service": "test.automation", + "data_template": { + "entity_id": "test.test_state", + "white_value": "{{white_value}}", + }, + }, + } + }, + } + }, + ) + self.hass.start() + self.hass.block_till_done() + + state = self.hass.states.get("light.test_template_light") + assert state.attributes.get("white_value") is None + + common.turn_on( + self.hass, "light.test_template_light", **{ATTR_WHITE_VALUE: 124} + ) + self.hass.block_till_done() + assert len(self.calls) == 1 + assert self.calls[0].data["white_value"] == "124" + + state = self.hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == 124 + + @pytest.mark.parametrize( + "expected_white_value,template", + [ + (255, "{{255}}"), + (None, "{{256}}"), + (None, "{{x - 12}}"), + (None, "{{ none }}"), + (None, ""), + ], + ) + def test_white_value_template(self, expected_white_value, template): + """Test the template for the white value.""" + with assert_setup_component(1, "light"): + assert setup.setup_component( + self.hass, + "light", + { + "light": { + "platform": "template", + "lights": { + "test_template_light": { + "value_template": "{{ 1 == 1 }}", + "turn_on": { + "service": "light.turn_on", + "entity_id": "light.test_state", + }, + "turn_off": { + "service": "light.turn_off", + "entity_id": "light.test_state", + }, + "set_white_value": { + "service": "light.turn_on", + "data_template": { + "entity_id": "light.test_state", + "white_value": "{{white_value}}", + }, + }, + "white_value_template": template, + } + }, + } + }, + ) + + self.hass.start() + self.hass.block_till_done() + + state = self.hass.states.get("light.test_template_light") + assert state is not None + assert state.attributes.get("white_value") == expected_white_value + def test_level_action_no_template(self): """Test setting brightness with optimistic template.""" assert setup.setup_component( @@ -955,7 +1055,6 @@ class TestTemplateLight: async def test_available_template_with_entities(hass): """Test availability templates with values from other entities.""" - await setup.async_setup_component( hass, "light",