mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add temperature support to light template (#30595)
* Add temperature support * Use guard clause
This commit is contained in:
parent
894b841a15
commit
1a1ef7680d
@ -5,8 +5,10 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
|
ATTR_COLOR_TEMP,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
|
SUPPORT_COLOR_TEMP,
|
||||||
Light,
|
Light,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -38,6 +40,8 @@ CONF_ON_ACTION = "turn_on"
|
|||||||
CONF_OFF_ACTION = "turn_off"
|
CONF_OFF_ACTION = "turn_off"
|
||||||
CONF_LEVEL_ACTION = "set_level"
|
CONF_LEVEL_ACTION = "set_level"
|
||||||
CONF_LEVEL_TEMPLATE = "level_template"
|
CONF_LEVEL_TEMPLATE = "level_template"
|
||||||
|
CONF_TEMPERATURE_TEMPLATE = "temperature_template"
|
||||||
|
CONF_TEMPERATURE_ACTION = "set_temperature"
|
||||||
|
|
||||||
LIGHT_SCHEMA = vol.Schema(
|
LIGHT_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -51,6 +55,8 @@ LIGHT_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_LEVEL_TEMPLATE): cv.template,
|
vol.Optional(CONF_LEVEL_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
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]
|
on_action = device_config[CONF_ON_ACTION]
|
||||||
off_action = device_config[CONF_OFF_ACTION]
|
off_action = device_config[CONF_OFF_ACTION]
|
||||||
level_action = device_config.get(CONF_LEVEL_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 = {
|
templates = {
|
||||||
CONF_VALUE_TEMPLATE: state_template,
|
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_ENTITY_PICTURE_TEMPLATE: entity_picture_template,
|
||||||
CONF_AVAILABILITY_TEMPLATE: availability_template,
|
CONF_AVAILABILITY_TEMPLATE: availability_template,
|
||||||
CONF_LEVEL_TEMPLATE: level_template,
|
CONF_LEVEL_TEMPLATE: level_template,
|
||||||
|
CONF_TEMPERATURE_TEMPLATE: temperature_template,
|
||||||
}
|
}
|
||||||
|
|
||||||
initialise_templates(hass, templates)
|
initialise_templates(hass, templates)
|
||||||
@ -101,6 +110,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||||||
level_action,
|
level_action,
|
||||||
level_template,
|
level_template,
|
||||||
entity_ids,
|
entity_ids,
|
||||||
|
temperature_action,
|
||||||
|
temperature_template,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,6 +140,8 @@ class LightTemplate(Light):
|
|||||||
level_action,
|
level_action,
|
||||||
level_template,
|
level_template,
|
||||||
entity_ids,
|
entity_ids,
|
||||||
|
temperature_action,
|
||||||
|
temperature_template,
|
||||||
):
|
):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
@ -146,11 +159,16 @@ class LightTemplate(Light):
|
|||||||
if level_action is not None:
|
if level_action is not None:
|
||||||
self._level_script = Script(hass, level_action)
|
self._level_script = Script(hass, level_action)
|
||||||
self._level_template = level_template
|
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._state = False
|
||||||
self._icon = None
|
self._icon = None
|
||||||
self._entity_picture = None
|
self._entity_picture = None
|
||||||
self._brightness = None
|
self._brightness = None
|
||||||
|
self._temperature = None
|
||||||
self._entities = entity_ids
|
self._entities = entity_ids
|
||||||
self._available = True
|
self._available = True
|
||||||
|
|
||||||
@ -164,12 +182,19 @@ class LightTemplate(Light):
|
|||||||
self._entity_picture_template.hass = self.hass
|
self._entity_picture_template.hass = self.hass
|
||||||
if self._availability_template is not None:
|
if self._availability_template is not None:
|
||||||
self._availability_template.hass = self.hass
|
self._availability_template.hass = self.hass
|
||||||
|
if self._temperature_template is not None:
|
||||||
|
self._temperature_template.hass = self.hass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brightness(self):
|
def brightness(self):
|
||||||
"""Return the brightness of the light."""
|
"""Return the brightness of the light."""
|
||||||
return self._brightness
|
return self._brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_temp(self):
|
||||||
|
"""Return the CT color value in mireds."""
|
||||||
|
return self._temperature
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the display name of this light."""
|
"""Return the display name of this light."""
|
||||||
@ -178,10 +203,12 @@ class LightTemplate(Light):
|
|||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
|
supported_features = 0
|
||||||
if self._level_script is not None:
|
if self._level_script is not None:
|
||||||
return SUPPORT_BRIGHTNESS
|
supported_features |= SUPPORT_BRIGHTNESS
|
||||||
|
if self._temperature_script is not None:
|
||||||
return 0
|
supported_features |= SUPPORT_COLOR_TEMP
|
||||||
|
return supported_features
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
@ -222,6 +249,7 @@ class LightTemplate(Light):
|
|||||||
if (
|
if (
|
||||||
self._template is not None
|
self._template is not None
|
||||||
or self._level_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
|
or self._availability_template is not None
|
||||||
):
|
):
|
||||||
async_track_state_change(
|
async_track_state_change(
|
||||||
@ -249,10 +277,22 @@ class LightTemplate(Light):
|
|||||||
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
self._brightness = kwargs[ATTR_BRIGHTNESS]
|
||||||
optimistic_set = True
|
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:
|
if ATTR_BRIGHTNESS in kwargs and self._level_script:
|
||||||
await self._level_script.async_run(
|
await self._level_script.async_run(
|
||||||
{"brightness": kwargs[ATTR_BRIGHTNESS]}, context=self._context
|
{"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:
|
else:
|
||||||
await self._on_script.async_run()
|
await self._on_script.async_run()
|
||||||
|
|
||||||
@ -272,6 +312,8 @@ class LightTemplate(Light):
|
|||||||
|
|
||||||
self.update_brightness()
|
self.update_brightness()
|
||||||
|
|
||||||
|
self.update_temperature()
|
||||||
|
|
||||||
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),
|
||||||
@ -311,35 +353,57 @@ class LightTemplate(Light):
|
|||||||
@callback
|
@callback
|
||||||
def update_brightness(self):
|
def update_brightness(self):
|
||||||
"""Update the brightness from the template."""
|
"""Update the brightness from the template."""
|
||||||
if self._level_template is not None:
|
if self._level_template is None:
|
||||||
try:
|
return
|
||||||
brightness = self._level_template.async_render()
|
try:
|
||||||
if 0 <= int(brightness) <= 255:
|
brightness = self._level_template.async_render()
|
||||||
self._brightness = int(brightness)
|
if 0 <= int(brightness) <= 255:
|
||||||
else:
|
self._brightness = int(brightness)
|
||||||
_LOGGER.error(
|
else:
|
||||||
"Received invalid brightness : %s. Expected: 0-255", brightness
|
_LOGGER.error(
|
||||||
)
|
"Received invalid brightness : %s. Expected: 0-255", brightness
|
||||||
self._brightness = None
|
)
|
||||||
except TemplateError as ex:
|
self._brightness = None
|
||||||
_LOGGER.error(ex)
|
except TemplateError as ex:
|
||||||
self._state = None
|
_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."""
|
||||||
if self._template is not None:
|
if self._template is None:
|
||||||
try:
|
return
|
||||||
state = self._template.async_render().lower()
|
try:
|
||||||
if state in _VALID_STATES:
|
state = self._template.async_render().lower()
|
||||||
self._state = state in ("true", STATE_ON)
|
if state in _VALID_STATES:
|
||||||
else:
|
self._state = state in ("true", STATE_ON)
|
||||||
_LOGGER.error(
|
else:
|
||||||
"Received invalid light is_on state: %s. Expected: %s",
|
_LOGGER.error(
|
||||||
state,
|
"Received invalid light is_on state: %s. Expected: %s",
|
||||||
", ".join(_VALID_STATES),
|
state,
|
||||||
)
|
", ".join(_VALID_STATES),
|
||||||
self._state = None
|
)
|
||||||
except TemplateError as ex:
|
|
||||||
_LOGGER.error(ex)
|
|
||||||
self._state = None
|
self._state = None
|
||||||
|
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
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import setup
|
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.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
@ -582,6 +582,98 @@ class TestTemplateLight:
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.attributes.get("brightness") == expected_level
|
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):
|
def test_friendly_name(self):
|
||||||
"""Test the accessibility of the friendly_name attribute."""
|
"""Test the accessibility of the friendly_name attribute."""
|
||||||
with assert_setup_component(1, "light"):
|
with assert_setup_component(1, "light"):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user