diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 9f099100084..a4cbce8182a 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -8,13 +8,14 @@ at https://home-assistant.io/components/automation/#numeric-state-trigger """ import logging +from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers.event import track_state_change +from homeassistant.util import template CONF_ENTITY_ID = "entity_id" CONF_BELOW = "below" CONF_ABOVE = "above" -CONF_ATTRIBUTE = "attribute" _LOGGER = logging.getLogger(__name__) @@ -29,7 +30,7 @@ def trigger(hass, config, action): below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) - attribute = config.get(CONF_ATTRIBUTE) + value_template = config.get(CONF_VALUE_TEMPLATE) if below is None and above is None: _LOGGER.error("Missing configuration key." @@ -37,13 +38,20 @@ def trigger(hass, config, action): CONF_BELOW, CONF_ABOVE) return False + if value_template is not None: + renderer = lambda value: template.render(hass, + value_template, + {'state': value}) + else: + renderer = lambda value: value.state + # pylint: disable=unused-argument def state_automation_listener(entity, from_s, to_s): """ Listens for state changes and calls action. """ # Fire action if we go from outside range into range - if _in_range(to_s, above, below, attribute) and \ - (from_s is None or not _in_range(from_s, above, below, attribute)): + if _in_range(above, below, renderer(to_s)) and \ + (from_s is None or not _in_range(above, below, renderer(from_s))): action() track_state_change( @@ -63,7 +71,7 @@ def if_action(hass, config): below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) - attribute = config.get(CONF_ATTRIBUTE) + value_template = config.get(CONF_VALUE_TEMPLATE) if below is None and above is None: _LOGGER.error("Missing configuration key." @@ -71,18 +79,23 @@ def if_action(hass, config): CONF_BELOW, CONF_ABOVE) return None + if value_template is not None: + renderer = lambda value: template.render(hass, + value_template, + {'state': value}) + else: + renderer = lambda value: value.state + def if_numeric_state(): """ Test numeric state condition. """ state = hass.states.get(entity_id) - return state is not None and _in_range(state, above, below, attribute) + return state is not None and _in_range(above, below, renderer(state)) return if_numeric_state -def _in_range(state, range_start, range_end, attribute): +def _in_range(range_start, range_end, value): """ Checks if value is inside the range """ - value = (state.state if attribute is None - else state.attributes.get(attribute)) try: value = float(value) except ValueError: diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 8280f396f93..49246e48117 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -295,7 +295,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -314,7 +314,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -333,7 +333,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -352,7 +352,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -371,7 +371,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -384,13 +384,51 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + def test_template_list(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute[2] }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + })) + # 3 is below 10 + self.hass.states.set('test.entity', 'entity', { 'test_attribute': [11, 15, 3] }) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_template_string(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'value_template': '{{ state.attributes.test_attribute | multiply(10) }}', + 'below': 10, + }, + 'action': { + 'service': 'test.automation' + } + } + })) + # 9 is below 10 + self.hass.states.set('test.entity', 'entity', { 'test_attribute': '0.9' }) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_not_fires_on_attribute_change_with_attribute_not_below_multiple_attributes(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'attribute': 'test_attribute', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': {