Add temperature support to light template (#30595)

* Add temperature support

* Use guard clause
This commit is contained in:
tetienne 2020-01-23 18:18:59 +01:00 committed by Paulus Schoutsen
parent 894b841a15
commit 1a1ef7680d
2 changed files with 187 additions and 31 deletions

View File

@ -5,8 +5,10 @@ import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ENTITY_ID_FORMAT,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP,
Light,
)
from homeassistant.const import (
@ -38,6 +40,8 @@ CONF_ON_ACTION = "turn_on"
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"
LIGHT_SCHEMA = vol.Schema(
{
@ -51,6 +55,8 @@ LIGHT_SCHEMA = vol.Schema(
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,
}
)
@ -75,6 +81,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
on_action = device_config[CONF_ON_ACTION]
off_action = device_config[CONF_OFF_ACTION]
level_action = device_config.get(CONF_LEVEL_ACTION)
temperature_action = device_config.get(CONF_TEMPERATURE_ACTION)
temperature_template = device_config.get(CONF_TEMPERATURE_TEMPLATE)
templates = {
CONF_VALUE_TEMPLATE: state_template,
@ -82,6 +90,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template,
CONF_AVAILABILITY_TEMPLATE: availability_template,
CONF_LEVEL_TEMPLATE: level_template,
CONF_TEMPERATURE_TEMPLATE: temperature_template,
}
initialise_templates(hass, templates)
@ -101,6 +110,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
level_action,
level_template,
entity_ids,
temperature_action,
temperature_template,
)
)
@ -129,6 +140,8 @@ class LightTemplate(Light):
level_action,
level_template,
entity_ids,
temperature_action,
temperature_template,
):
"""Initialize the light."""
self.hass = hass
@ -146,11 +159,16 @@ class LightTemplate(Light):
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._state = False
self._icon = None
self._entity_picture = None
self._brightness = None
self._temperature = None
self._entities = entity_ids
self._available = True
@ -164,12 +182,19 @@ class LightTemplate(Light):
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
@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 name(self):
"""Return the display name of this light."""
@ -178,10 +203,12 @@ class LightTemplate(Light):
@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
return supported_features
@property
def is_on(self):
@ -222,6 +249,7 @@ class LightTemplate(Light):
if (
self._template is not None
or self._level_template is not None
or self._temperature_template is not None
or self._availability_template is not None
):
async_track_state_change(
@ -249,10 +277,22 @@ class LightTemplate(Light):
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 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
)
else:
await self._on_script.async_run()
@ -272,6 +312,8 @@ class LightTemplate(Light):
self.update_brightness()
self.update_temperature()
for property_name, template in (
("_icon", self._icon_template),
("_entity_picture", self._entity_picture_template),
@ -311,7 +353,8 @@ class LightTemplate(Light):
@callback
def update_brightness(self):
"""Update the brightness from the template."""
if self._level_template is not None:
if self._level_template is None:
return
try:
brightness = self._level_template.async_render()
if 0 <= int(brightness) <= 255:
@ -328,7 +371,8 @@ class LightTemplate(Light):
@callback
def update_state(self):
"""Update the state from the template."""
if self._template is not None:
if self._template is None:
return
try:
state = self._template.async_render().lower()
if state in _VALID_STATES:
@ -343,3 +387,23 @@ class LightTemplate(Light):
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
@callback
def update_temperature(self):
"""Update the temperature from the template."""
if self._temperature_template is None:
return
try:
temperature = int(self._temperature_template.async_render())
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
except TemplateError:
_LOGGER.error("Cannot evaluate temperature template", exc_info=True)
self._temperature = None

View File

@ -4,7 +4,7 @@ import logging
import pytest
from homeassistant import setup
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import callback
@ -582,6 +582,98 @@ class TestTemplateLight:
assert state is not None
assert state.attributes.get("brightness") == expected_level
@pytest.mark.parametrize(
"expected_temp,template",
[(500, "{{500}}"), (None, "{{501}}"), (None, "{{x - 12}}")],
)
def test_temperature_template(self, expected_temp, template):
"""Test the template for the temperature."""
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_temperature": {
"service": "light.turn_on",
"data_template": {
"entity_id": "light.test_state",
"color_temp": "{{color_temp}}",
},
},
"temperature_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("color_temp") == expected_temp
def test_temperature_action_no_template(self):
"""Test setting temperature 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_temperature": {
"service": "test.automation",
"data_template": {
"entity_id": "test.test_state",
"color_temp": "{{color_temp}}",
},
},
}
},
}
},
)
self.hass.start()
self.hass.block_till_done()
state = self.hass.states.get("light.test_template_light")
assert state.attributes.get("color_template") is None
common.turn_on(self.hass, "light.test_template_light", **{ATTR_COLOR_TEMP: 345})
self.hass.block_till_done()
assert len(self.calls) == 1
assert self.calls[0].data["color_temp"] == "345"
state = self.hass.states.get("light.test_template_light")
_LOGGER.info(str(state.attributes))
assert state is not None
assert state.attributes.get("color_temp") == 345
def test_friendly_name(self):
"""Test the accessibility of the friendly_name attribute."""
with assert_setup_component(1, "light"):