Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -326,7 +326,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 @@ -5,6 +5,6 @@
"requirements": [],
"dependencies": [],
"codeowners": [
"@PhracturedBlue"
"@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