mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 09:47:13 +00:00
Add color to light template (#31435)
* Add color support to light template * Add tests about color support
This commit is contained in:
parent
45c997ea04
commit
8bc77f0421
@ -6,8 +6,10 @@ import voluptuous as vol
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
ENTITY_ID_FORMAT,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
Light,
|
||||
)
|
||||
@ -42,6 +44,8 @@ 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(
|
||||
{
|
||||
@ -57,6 +61,8 @@ LIGHT_SCHEMA = vol.Schema(
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
@ -76,14 +82,19 @@ 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,
|
||||
@ -91,6 +102,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
CONF_AVAILABILITY_TEMPLATE: availability_template,
|
||||
CONF_LEVEL_TEMPLATE: level_template,
|
||||
CONF_TEMPERATURE_TEMPLATE: temperature_template,
|
||||
CONF_COLOR_TEMPLATE: color_template,
|
||||
}
|
||||
|
||||
initialise_templates(hass, templates)
|
||||
@ -114,6 +126,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||
entity_ids,
|
||||
temperature_action,
|
||||
temperature_template,
|
||||
color_action,
|
||||
color_template,
|
||||
)
|
||||
)
|
||||
|
||||
@ -144,6 +158,8 @@ class LightTemplate(Light):
|
||||
entity_ids,
|
||||
temperature_action,
|
||||
temperature_template,
|
||||
color_action,
|
||||
color_template,
|
||||
):
|
||||
"""Initialize the light."""
|
||||
self.hass = hass
|
||||
@ -165,12 +181,17 @@ class LightTemplate(Light):
|
||||
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
|
||||
|
||||
@ -184,6 +205,11 @@ class LightTemplate(Light):
|
||||
"""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."""
|
||||
@ -197,6 +223,8 @@ class LightTemplate(Light):
|
||||
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
|
||||
@ -239,6 +267,7 @@ class LightTemplate(Light):
|
||||
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(
|
||||
@ -282,6 +311,12 @@ class LightTemplate(Light):
|
||||
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(
|
||||
{"hs": hs_value, "h": int(hs_value[0]), "s": int(hs_value[1])},
|
||||
context=self._context,
|
||||
)
|
||||
else:
|
||||
await self._on_script.async_run()
|
||||
|
||||
@ -303,6 +338,8 @@ class LightTemplate(Light):
|
||||
|
||||
self.update_temperature()
|
||||
|
||||
self.update_color()
|
||||
|
||||
for property_name, template in (
|
||||
("_icon", self._icon_template),
|
||||
("_entity_picture", self._entity_picture_template),
|
||||
@ -396,3 +433,34 @@ class LightTemplate(Light):
|
||||
except TemplateError:
|
||||
_LOGGER.error("Cannot evaluate temperature template", exc_info=True)
|
||||
self._temperature = None
|
||||
|
||||
@callback
|
||||
def update_color(self):
|
||||
"""Update the hs_color from the template."""
|
||||
if self._color_template is None:
|
||||
return
|
||||
|
||||
self._color = None
|
||||
|
||||
try:
|
||||
render = self._color_template.async_render()
|
||||
h_str, s_str = map(
|
||||
float, render.replace("(", "").replace(")", "").split(",", 1)
|
||||
)
|
||||
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,
|
||||
)
|
||||
else:
|
||||
_LOGGER.error("Received invalid hs_color : (%s)", render)
|
||||
except TemplateError as ex:
|
||||
_LOGGER.error(ex)
|
||||
|
@ -4,7 +4,11 @@ import logging
|
||||
import pytest
|
||||
|
||||
from homeassistant import setup
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_HS_COLOR,
|
||||
)
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||
from homeassistant.core import callback
|
||||
|
||||
@ -816,6 +820,123 @@ class TestTemplateLight:
|
||||
|
||||
assert state.attributes["entity_picture"] == "/local/light.png"
|
||||
|
||||
def test_color_action_no_template(self):
|
||||
"""Test setting color 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_color": [
|
||||
{
|
||||
"service": "test.automation",
|
||||
"data_template": {
|
||||
"entity_id": "test.test_state",
|
||||
"h": "{{h}}",
|
||||
"s": "{{s}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
"service": "test.automation",
|
||||
"data_template": {
|
||||
"entity_id": "test.test_state",
|
||||
"s": "{{s}}",
|
||||
"h": "{{h}}",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
self.hass.start()
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get("light.test_template_light")
|
||||
assert state.attributes.get("hs_color") is None
|
||||
|
||||
common.turn_on(
|
||||
self.hass, "light.test_template_light", **{ATTR_HS_COLOR: (40, 50)}
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
assert len(self.calls) == 2
|
||||
assert self.calls[0].data["h"] == "40"
|
||||
assert self.calls[0].data["s"] == "50"
|
||||
assert self.calls[1].data["h"] == "40"
|
||||
assert self.calls[1].data["s"] == "50"
|
||||
|
||||
state = self.hass.states.get("light.test_template_light")
|
||||
_LOGGER.info(str(state.attributes))
|
||||
assert state is not None
|
||||
assert self.calls[0].data["h"] == "40"
|
||||
assert self.calls[0].data["s"] == "50"
|
||||
assert self.calls[1].data["h"] == "40"
|
||||
assert self.calls[1].data["s"] == "50"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expected_hs,template",
|
||||
[
|
||||
((360, 100), "{{(360, 100)}}"),
|
||||
((359.9, 99.9), "{{(359.9, 99.9)}}"),
|
||||
(None, "{{(361, 100)}}"),
|
||||
(None, "{{(360, 101)}}"),
|
||||
(None, "{{x - 12}}"),
|
||||
],
|
||||
)
|
||||
def test_color_template(self, expected_hs, template):
|
||||
"""Test the template for the color."""
|
||||
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_color": [
|
||||
{
|
||||
"service": "input_number.set_value",
|
||||
"data_template": {
|
||||
"entity_id": "input_number.h",
|
||||
"color_temp": "{{h}}",
|
||||
},
|
||||
}
|
||||
],
|
||||
"color_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("hs_color") == expected_hs
|
||||
|
||||
|
||||
async def test_available_template_with_entities(hass):
|
||||
"""Test availability templates with values from other entities."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user