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>
This commit is contained in:
parent
2a6948e696
commit
c5d2e44baf
2 changed files with 168 additions and 2 deletions
|
@ -7,10 +7,12 @@ from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
|
ATTR_WHITE_VALUE,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
SUPPORT_COLOR,
|
SUPPORT_COLOR,
|
||||||
SUPPORT_COLOR_TEMP,
|
SUPPORT_COLOR_TEMP,
|
||||||
|
SUPPORT_WHITE_VALUE,
|
||||||
Light,
|
Light,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -46,6 +48,8 @@ CONF_TEMPERATURE_TEMPLATE = "temperature_template"
|
||||||
CONF_TEMPERATURE_ACTION = "set_temperature"
|
CONF_TEMPERATURE_ACTION = "set_temperature"
|
||||||
CONF_COLOR_TEMPLATE = "color_template"
|
CONF_COLOR_TEMPLATE = "color_template"
|
||||||
CONF_COLOR_ACTION = "set_color"
|
CONF_COLOR_ACTION = "set_color"
|
||||||
|
CONF_WHITE_VALUE_TEMPLATE = "white_value_template"
|
||||||
|
CONF_WHITE_VALUE_ACTION = "set_white_value"
|
||||||
|
|
||||||
LIGHT_SCHEMA = vol.Schema(
|
LIGHT_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -63,6 +67,8 @@ LIGHT_SCHEMA = vol.Schema(
|
||||||
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_TEMPERATURE_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_COLOR_TEMPLATE): cv.template,
|
vol.Optional(CONF_COLOR_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA,
|
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_action = device_config.get(CONF_COLOR_ACTION)
|
||||||
color_template = device_config.get(CONF_COLOR_TEMPLATE)
|
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 = {
|
templates = {
|
||||||
CONF_VALUE_TEMPLATE: state_template,
|
CONF_VALUE_TEMPLATE: state_template,
|
||||||
CONF_ICON_TEMPLATE: icon_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_LEVEL_TEMPLATE: level_template,
|
||||||
CONF_TEMPERATURE_TEMPLATE: temperature_template,
|
CONF_TEMPERATURE_TEMPLATE: temperature_template,
|
||||||
CONF_COLOR_TEMPLATE: color_template,
|
CONF_COLOR_TEMPLATE: color_template,
|
||||||
|
CONF_WHITE_VALUE_TEMPLATE: white_value_template,
|
||||||
}
|
}
|
||||||
|
|
||||||
initialise_templates(hass, templates)
|
initialise_templates(hass, templates)
|
||||||
|
@ -128,6 +138,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||||
temperature_template,
|
temperature_template,
|
||||||
color_action,
|
color_action,
|
||||||
color_template,
|
color_template,
|
||||||
|
white_value_action,
|
||||||
|
white_value_template,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -155,6 +167,8 @@ class LightTemplate(Light):
|
||||||
temperature_template,
|
temperature_template,
|
||||||
color_action,
|
color_action,
|
||||||
color_template,
|
color_template,
|
||||||
|
white_value_action,
|
||||||
|
white_value_template,
|
||||||
):
|
):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
@ -180,6 +194,10 @@ class LightTemplate(Light):
|
||||||
if color_action is not None:
|
if color_action is not None:
|
||||||
self._color_script = Script(hass, color_action)
|
self._color_script = Script(hass, color_action)
|
||||||
self._color_template = color_template
|
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._state = False
|
||||||
self._icon = None
|
self._icon = None
|
||||||
|
@ -187,6 +205,7 @@ class LightTemplate(Light):
|
||||||
self._brightness = None
|
self._brightness = None
|
||||||
self._temperature = None
|
self._temperature = None
|
||||||
self._color = None
|
self._color = None
|
||||||
|
self._white_value = None
|
||||||
self._entities = entity_ids
|
self._entities = entity_ids
|
||||||
self._available = True
|
self._available = True
|
||||||
|
|
||||||
|
@ -200,6 +219,11 @@ class LightTemplate(Light):
|
||||||
"""Return the CT color value in mireds."""
|
"""Return the CT color value in mireds."""
|
||||||
return self._temperature
|
return self._temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def white_value(self):
|
||||||
|
"""Return the white value."""
|
||||||
|
return self._white_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hs_color(self):
|
def hs_color(self):
|
||||||
"""Return the hue and saturation color value [float, float]."""
|
"""Return the hue and saturation color value [float, float]."""
|
||||||
|
@ -220,6 +244,8 @@ class LightTemplate(Light):
|
||||||
supported_features |= SUPPORT_COLOR_TEMP
|
supported_features |= SUPPORT_COLOR_TEMP
|
||||||
if self._color_script is not None:
|
if self._color_script is not None:
|
||||||
supported_features |= SUPPORT_COLOR
|
supported_features |= SUPPORT_COLOR
|
||||||
|
if self._white_value_script is not None:
|
||||||
|
supported_features |= SUPPORT_WHITE_VALUE
|
||||||
return supported_features
|
return supported_features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -263,6 +289,7 @@ class LightTemplate(Light):
|
||||||
or self._level_template is not None
|
or self._level_template is not None
|
||||||
or self._temperature_template is not None
|
or self._temperature_template is not None
|
||||||
or self._color_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
|
or self._availability_template is not None
|
||||||
):
|
):
|
||||||
async_track_state_change(
|
async_track_state_change(
|
||||||
|
@ -290,6 +317,13 @@ class LightTemplate(Light):
|
||||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||||
optimistic_set = True
|
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:
|
if self._temperature_template is None and ATTR_COLOR_TEMP in kwargs:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Optimistically setting color temperature to %s",
|
"Optimistically setting color temperature to %s",
|
||||||
|
@ -306,6 +340,10 @@ class LightTemplate(Light):
|
||||||
await self._temperature_script.async_run(
|
await self._temperature_script.async_run(
|
||||||
{"color_temp": kwargs[ATTR_COLOR_TEMP]}, context=self._context
|
{"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:
|
elif ATTR_HS_COLOR in kwargs and self._color_script:
|
||||||
hs_value = kwargs[ATTR_HS_COLOR]
|
hs_value = kwargs[ATTR_HS_COLOR]
|
||||||
await self._color_script.async_run(
|
await self._color_script.async_run(
|
||||||
|
@ -335,6 +373,8 @@ class LightTemplate(Light):
|
||||||
|
|
||||||
self.update_color()
|
self.update_color()
|
||||||
|
|
||||||
|
self.update_white_value()
|
||||||
|
|
||||||
for property_name, template in (
|
for property_name, template in (
|
||||||
("_icon", self._icon_template),
|
("_icon", self._icon_template),
|
||||||
("_entity_picture", self._entity_picture_template),
|
("_entity_picture", self._entity_picture_template),
|
||||||
|
@ -398,6 +438,33 @@ class LightTemplate(Light):
|
||||||
_LOGGER.error("Invalid template", exc_info=True)
|
_LOGGER.error("Invalid template", exc_info=True)
|
||||||
self._brightness = None
|
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
|
@callback
|
||||||
def update_state(self):
|
def update_state(self):
|
||||||
"""Update the state from the template."""
|
"""Update the state from the template."""
|
||||||
|
|
|
@ -8,6 +8,7 @@ from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_TEMP,
|
ATTR_COLOR_TEMP,
|
||||||
ATTR_HS_COLOR,
|
ATTR_HS_COLOR,
|
||||||
|
ATTR_WHITE_VALUE,
|
||||||
)
|
)
|
||||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
@ -35,7 +36,7 @@ class TestTemplateLight:
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def record_call(service):
|
def record_call(service):
|
||||||
"""Track function calls.."""
|
"""Track function calls."""
|
||||||
self.calls.append(service)
|
self.calls.append(service)
|
||||||
|
|
||||||
self.hass.services.register("test", "automation", record_call)
|
self.hass.services.register("test", "automation", record_call)
|
||||||
|
@ -494,6 +495,105 @@ class TestTemplateLight:
|
||||||
state = self.hass.states.get("light.test_template_light")
|
state = self.hass.states.get("light.test_template_light")
|
||||||
assert state.state == STATE_OFF
|
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):
|
def test_level_action_no_template(self):
|
||||||
"""Test setting brightness with optimistic template."""
|
"""Test setting brightness with optimistic template."""
|
||||||
assert setup.setup_component(
|
assert setup.setup_component(
|
||||||
|
@ -955,7 +1055,6 @@ class TestTemplateLight:
|
||||||
|
|
||||||
async def test_available_template_with_entities(hass):
|
async def test_available_template_with_entities(hass):
|
||||||
"""Test availability templates with values from other entities."""
|
"""Test availability templates with values from other entities."""
|
||||||
|
|
||||||
await setup.async_setup_component(
|
await setup.async_setup_component(
|
||||||
hass,
|
hass,
|
||||||
"light",
|
"light",
|
||||||
|
|
Loading…
Add table
Reference in a new issue