Skip to content
Merged
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 @@ -329,7 +329,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
70 changes: 39 additions & 31 deletions homeassistant/components/template/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,38 +267,10 @@ 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

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
self.update_brightness()

for property_name, template in (
("_icon", self._icon_template),
Expand Down Expand Up @@ -335,3 +307,39 @@ async def async_update(self):
self._name,
ex,
)

@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()
if 0 <= int(brightness) <= 255:
self._brightness = int(brightness)
else:
_LOGGER.error(
"Received invalid brightness : %s. Expected: 0-255", brightness
)
self._brightness = None
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None

@callback
def update_state(self):
"""Update the state from the template."""
if self._template is not None:
try:
state = self._template.async_render().lower()
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
except TemplateError as ex:
_LOGGER.error(ex)
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 @@ -4,5 +4,5 @@
"documentation": "https://www.home-assistant.io/integrations/template",
"requirements": [],
"dependencies": [],
"codeowners": ["@PhracturedBlue"]
"codeowners": ["@PhracturedBlue", "@tetienne"]
}
180 changes: 63 additions & 117 deletions tests/components/template/test_light.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""The tests for the Template light platform."""
import logging

import pytest

from homeassistant import setup
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
Expand Down Expand Up @@ -38,8 +40,8 @@ def teardown_method(self, method):
"""Stop everything that was started."""
self.hass.stop()

def test_template_state_text(self):
"""Test the state text of a template."""
def test_template_state_invalid(self):
"""Test template state with render error."""
with assert_setup_component(1, "light"):
assert setup.setup_component(
self.hass,
Expand All @@ -49,7 +51,7 @@ def test_template_state_text(self):
"platform": "template",
"lights": {
"test_template_light": {
"value_template": "{{ states.light.test_state.state }}",
"value_template": "{{states.test['big.fat...']}}",
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
Expand All @@ -74,20 +76,11 @@ def test_template_state_text(self):
self.hass.start()
self.hass.block_till_done()

state = self.hass.states.set("light.test_state", STATE_ON)
self.hass.block_till_done()

state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_ON

state = self.hass.states.set("light.test_state", STATE_OFF)
self.hass.block_till_done()

state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_OFF

def test_template_state_boolean_on(self):
"""Test the setting of the state with boolean on."""
def test_template_state_text(self):
"""Test the state text of a template."""
with assert_setup_component(1, "light"):
assert setup.setup_component(
self.hass,
Expand All @@ -97,7 +90,7 @@ def test_template_state_boolean_on(self):
"platform": "template",
"lights": {
"test_template_light": {
"value_template": "{{ 1 == 1 }}",
"value_template": "{{ states.light.test_state.state }}",
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
Expand All @@ -122,11 +115,24 @@ def test_template_state_boolean_on(self):
self.hass.start()
self.hass.block_till_done()

state = self.hass.states.set("light.test_state", STATE_ON)
self.hass.block_till_done()

state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_ON

def test_template_state_boolean_off(self):
"""Test the setting of the state with off."""
state = self.hass.states.set("light.test_state", STATE_OFF)
self.hass.block_till_done()

state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_OFF

@pytest.mark.parametrize(
"expected_state,template",
[(STATE_ON, "{{ 1 == 1 }}"), (STATE_OFF, "{{ 1 == 2 }}")],
)
def test_template_state_boolean(self, expected_state, template):
"""Test the setting of the state with boolean on."""
with assert_setup_component(1, "light"):
assert setup.setup_component(
self.hass,
Expand All @@ -136,7 +142,7 @@ def test_template_state_boolean_off(self):
"platform": "template",
"lights": {
"test_template_light": {
"value_template": "{{ 1 == 2 }}",
"value_template": template,
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
Expand All @@ -162,7 +168,7 @@ def test_template_state_boolean_off(self):
self.hass.block_till_done()

state = self.hass.states.get("light.test_template_light")
assert state.state == STATE_OFF
assert state.state == expected_state

def test_template_syntax_error(self):
"""Test templating syntax error."""
Expand Down Expand Up @@ -271,110 +277,47 @@ def test_no_lights_does_not_create(self):

assert self.hass.states.all() == []

def test_missing_template_does_create(self):
@pytest.mark.parametrize(
"missing_key, count", [("value_template", 1), ("turn_on", 0), ("turn_off", 0)]
)
def test_missing_key(self, missing_key, count):
"""Test missing template."""
with assert_setup_component(1, "light"):
assert setup.setup_component(
self.hass,
"light",
{
"light": {
"platform": "template",
"lights": {
"light_one": {
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
},
"turn_off": {
"service": "light.turn_off",
"entity_id": "light.test_state",
},
"set_level": {
"service": "light.turn_on",
"data_template": {
"entity_id": "light.test_state",
"brightness": "{{brightness}}",
},
},
}
light = {
"light": {
"platform": "template",
"lights": {
"light_one": {
"value_template": "{{ 1== 1}}",
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
},
}
},
)

self.hass.start()
self.hass.block_till_done()

assert self.hass.states.all() != []

def test_missing_on_does_not_create(self):
"""Test missing on."""
with assert_setup_component(0, "light"):
assert setup.setup_component(
self.hass,
"light",
{
"light": {
"platform": "template",
"lights": {
"bad name here": {
"value_template": "{{ 1== 1}}",
"turn_off": {
"service": "light.turn_off",
"entity_id": "light.test_state",
},
"set_level": {
"service": "light.turn_on",
"data_template": {
"entity_id": "light.test_state",
"brightness": "{{brightness}}",
},
},
}
"turn_off": {
"service": "light.turn_off",
"entity_id": "light.test_state",
},
}
},
)

self.hass.start()
self.hass.block_till_done()

assert self.hass.states.all() == []

def test_missing_off_does_not_create(self):
"""Test missing off."""
with assert_setup_component(0, "light"):
assert setup.setup_component(
self.hass,
"light",
{
"light": {
"platform": "template",
"lights": {
"bad name here": {
"value_template": "{{ 1== 1}}",
"turn_on": {
"service": "light.turn_on",
"entity_id": "light.test_state",
},
"set_level": {
"service": "light.turn_on",
"data_template": {
"entity_id": "light.test_state",
"brightness": "{{brightness}}",
},
},
}
"set_level": {
"service": "light.turn_on",
"data_template": {
"entity_id": "light.test_state",
"brightness": "{{brightness}}",
},
},
}
},
)
}
}

del light["light"]["lights"]["light_one"][missing_key]
with assert_setup_component(count, "light"):
assert setup.setup_component(self.hass, "light", light)
self.hass.start()
self.hass.block_till_done()

assert self.hass.states.all() == []
if count:
assert self.hass.states.all() != []
else:
assert self.hass.states.all() == []

def test_on_action(self):
"""Test on action."""
Expand Down Expand Up @@ -594,7 +537,11 @@ def test_level_action_no_template(self):
assert state is not None
assert state.attributes.get("brightness") == 124

def test_level_template(self):
@pytest.mark.parametrize(
"expected_level,template",
[(255, "{{255}}"), (None, "{{256}}"), (None, "{{x - 12}}")],
)
def test_level_template(self, expected_level, template):
"""Test the template for the level."""
with assert_setup_component(1, "light"):
assert setup.setup_component(
Expand All @@ -621,7 +568,7 @@ def test_level_template(self):
"brightness": "{{brightness}}",
},
},
"level_template": "{{42}}",
"level_template": template,
}
},
}
Expand All @@ -633,8 +580,7 @@ def test_level_template(self):

state = self.hass.states.get("light.test_template_light")
assert state is not None

assert state.attributes.get("brightness") == 42
assert state.attributes.get("brightness") == expected_level

def test_friendly_name(self):
"""Test the accessibility of the friendly_name attribute."""
Expand Down