Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ homeassistant/components/tado/* @michaelarnauts
homeassistant/components/tahoma/* @philklei
homeassistant/components/tautulli/* @ludeeus
homeassistant/components/tellduslive/* @fredrike
homeassistant/components/template/* @PhracturedBlue
homeassistant/components/template/* @PhracturedBlue @tetienne
homeassistant/components/tesla/* @zabuldon @alandtse
homeassistant/components/tfiac/* @fredrike @mellado
homeassistant/components/thethingsnetwork/* @fabaff
Expand Down
210 changes: 176 additions & 34 deletions homeassistant/components/template/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@

from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ENTITY_ID_FORMAT,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
Light,
)
from homeassistant.const import (
Expand Down Expand Up @@ -38,6 +42,10 @@
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"
CONF_COLOR_TEMPLATE = "color_template"
CONF_COLOR_ACTION = "set_color"

LIGHT_SCHEMA = vol.Schema(
{
Expand All @@ -51,6 +59,10 @@
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,
vol.Optional(CONF_COLOR_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_ACTION): cv.SCRIPT_SCHEMA,
}
)

Expand All @@ -70,18 +82,24 @@ 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,
CONF_ENTITY_PICTURE_TEMPLATE: entity_picture_template,
CONF_AVAILABILITY_TEMPLATE: availability_template,
CONF_LEVEL_TEMPLATE: level_template,
CONF_TEMPERATURE_TEMPLATE: temperature_template,
CONF_COLOR_TEMPLATE: color_template,
}

initialise_templates(hass, templates)
Expand All @@ -101,6 +119,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
level_action,
level_template,
entity_ids,
temperature_action,
temperature_template,
color_action,
color_template,
)
)

Expand Down Expand Up @@ -129,6 +151,10 @@ def __init__(
level_action,
level_template,
entity_ids,
temperature_action,
temperature_template,
color_action,
color_template,
):
"""Initialize the light."""
self.hass = hass
Expand All @@ -146,11 +172,21 @@ def __init__(
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._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

Expand All @@ -164,12 +200,26 @@ def __init__(
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
if self._color_template is not None:
self._color_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 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."""
Expand All @@ -178,10 +228,14 @@ def name(self):
@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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that this is based off the existence of a script, not on a value template ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I just follow the existing solution. But IMO it seems logic: you need an action to be able to change the brightness/color/temperature.

supported_features |= SUPPORT_COLOR_TEMP
if self._color_script is not None:
supported_features |= SUPPORT_COLOR
return supported_features

@property
def is_on(self):
Expand Down Expand Up @@ -222,6 +276,8 @@ def template_light_startup(event):
if (
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(
Expand Down Expand Up @@ -249,10 +305,33 @@ async def async_turn_on(self, **kwargs):
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 self._color_template is None and ATTR_HS_COLOR in kwargs:
_LOGGER.info("Optimistically setting color to %s", kwargs[ATTR_HS_COLOR])
self._color = kwargs[ATTR_HS_COLOR]
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
)
elif ATTR_HS_COLOR in kwargs and self._color_script:
hs_value = kwargs[ATTR_HS_COLOR]
await self._color_script.async_run(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are all these different scripts? Since it is all done inside the async_turn_on method, shouldn't there just be a generic turn_on script ?

@tetienne tetienne Dec 10, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these scripts map the actions to call when the brightness/color/temperature is updated. See https://www.home-assistant.io/integrations/light.template

{"hs": hs_value, "h": int(hs_value[0]), "s": int(hs_value[1])},
context=self._context,
)
else:
await self._on_script.async_run()

Expand All @@ -267,38 +346,14 @@ async def async_turn_off(self, **kwargs):
self.async_schedule_update_ha_state()

async def async_update(self):
"""Update the state from the template."""
if self._template is not None:
try:
state = self._template.async_render().lower()
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
"""Update from templates."""
self.update_state()

if state in _VALID_STATES:
self._state = state in ("true", STATE_ON)
else:
_LOGGER.error(
"Received invalid light is_on state: %s. Expected: %s",
state,
", ".join(_VALID_STATES),
)
self._state = None
self.update_brightness()

if self._level_template is not None:
try:
brightness = self._level_template.async_render()
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None
self.update_temperature()

if 0 <= int(brightness) <= 255:
self._brightness = int(brightness)
else:
_LOGGER.error(
"Received invalid brightness : %s. Expected: 0-255", brightness
)
self._brightness = None
self.update_color()

for property_name, template in (
("_icon", self._icon_template),
Expand All @@ -320,7 +375,7 @@ async def async_update(self):
):
# Common during HA startup - so just a warning
_LOGGER.warning(
"Could not render %s template %s," " the state is unknown.",
"Could not render %s template %s, the state is unknown.",
friendly_property_name,
self._name,
)
Expand All @@ -335,3 +390,90 @@ async def async_update(self):
self._name,
ex,
)

@callback
def update_temperature(self):
"""Update the temperature from the template."""
if self._temperature_template is not None:
try:
temperature = int(self._temperature_template.async_render())
except TemplateError:
_LOGGER.error("Cannot evaluate temperature template", exc_info=True)
self._temperature = None

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

@callback
def update_brightness(self):
"""Update the brightness from the template."""
if self._level_template is not None:
try:
brightness = self._level_template.async_render()
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None

if 0 <= int(brightness) <= 255:
self._brightness = int(brightness)
else:
_LOGGER.error(
"Received invalid brightness : %s. Expected: 0-255", brightness
)
self._brightness = None

@callback
def update_color(self):
"""Update the hs_color from the template."""
if self._color_template is not None:
h_str = None
s_str = None
self._color = None

try:
render = self._color_template.async_render()
h_str, s_str = map(
int, render.replace("(", "").replace(")", "").split(",", 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be float

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a hue lamp
FireShot Capture 032 - Home Assistant - hassio lan

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn't know. I will change this.

)
except TemplateError as ex:
_LOGGER.error(ex)
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,
)

@callback
def update_state(self):
"""Update the state from the template."""
if self._template is not None:
try:
state = self._template.async_render().lower()
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None

if state in _VALID_STATES:
self._state = state in ("true", STATE_ON)
else:
_LOGGER.error(
"Received invalid light is_on state: %s. Expected: %s",
state,
", ".join(_VALID_STATES),
)
self._state = None
2 changes: 1 addition & 1 deletion homeassistant/components/template/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"requirements": [],
"dependencies": [],
"codeowners": [
"@PhracturedBlue"
"@PhracturedBlue", "@tetienne"
]
}
Loading