From 53239387e066d4744666f5c8123216c50b1169da Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Mon, 14 Dec 2015 11:08:31 -0700 Subject: [PATCH 1/6] Add support for template --- .../components/automation/numeric_state.py | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 9f099100084..93fedcc8e3b 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,18 @@ 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, value) + else: + renderer = None + # 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(to_s, above, below, renderer) and \ + (from_s is None or not _in_range(from_s, above, below, renderer)): action() track_state_change( @@ -63,7 +69,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 +77,29 @@ 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, value) + else: + renderer = None + 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(state, above, below, + renderer) return if_numeric_state -def _in_range(state, range_start, range_end, attribute): +def _in_range(state, range_start, range_end, renderer): """ Checks if value is inside the range """ - value = (state.state if attribute is None - else state.attributes.get(attribute)) + + if renderer is not None: + value = renderer({'value': state}) + else: + # If no renderer is provided, just assume they want the state + value = state.state + try: value = float(value) except ValueError: From cec62bdf874d3963096b4ce469b679487e3d08f0 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Mon, 14 Dec 2015 14:47:32 -0700 Subject: [PATCH 2/6] Add tests --- .../automation/test_numeric_state.py | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 8280f396f93..9e0301168f2 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': '{{ value.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': '{{ value.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': '{{ value.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': '{{ value.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': '{{ value.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': '{{ value.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': '{{ value.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': '{{ value.attributes.test_attribute }}', 'below': 10, }, 'action': { From 91a945f4c73d4c539e14c73baec0c153eb3bd61d Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Mon, 14 Dec 2015 15:01:38 -0700 Subject: [PATCH 3/6] Slight style change --- homeassistant/components/automation/numeric_state.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 93fedcc8e3b..40249372b43 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -85,8 +85,7 @@ def if_action(hass, config): 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, - renderer) + return state is not None and _in_range(state, above, below, renderer) return if_numeric_state From a517784c9e6d4d67e51652f58ade87430376e1f0 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Mon, 14 Dec 2015 15:07:35 -0700 Subject: [PATCH 4/6] Condense in_range template logic --- homeassistant/components/automation/numeric_state.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 40249372b43..d73b7061eb8 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -92,12 +92,7 @@ def if_action(hass, config): def _in_range(state, range_start, range_end, renderer): """ Checks if value is inside the range """ - - if renderer is not None: - value = renderer({'value': state}) - else: - # If no renderer is provided, just assume they want the state - value = state.state + value = state.state if renderer is None else renderer({'value': state}) try: value = float(value) From 455a9c83a6338e9ad52198bdf6a0f56f9197ef5c Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Tue, 15 Dec 2015 08:57:30 -0700 Subject: [PATCH 5/6] Simplify logic --- .../components/automation/numeric_state.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index d73b7061eb8..10a14b179a8 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -39,17 +39,19 @@ def trigger(hass, config, action): return False if value_template is not None: - renderer = lambda value: template.render(hass, value_template, value) + renderer = lambda value: template.render(hass, + value_template, + {'value': value}) else: - renderer = None + 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, renderer) and \ - (from_s is None or not _in_range(from_s, above, below, renderer)): + 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( @@ -78,22 +80,22 @@ def if_action(hass, config): return None if value_template is not None: - renderer = lambda value: template.render(hass, value_template, value) + renderer = lambda value: template.render(hass, + value_template, + {'value': value}) else: - renderer = None + 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, renderer) + return state is not None and _in_range(above, below, renderer(state)) return if_numeric_state -def _in_range(state, range_start, range_end, renderer): +def _in_range(range_start, range_end, value): """ Checks if value is inside the range """ - value = state.state if renderer is None else renderer({'value': state}) - try: value = float(value) except ValueError: From 9fa8b27d65330f5267efa83fb0e4ec7af6b9cf9d Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Tue, 15 Dec 2015 10:12:43 -0700 Subject: [PATCH 6/6] Change from `value` to `state` --- .../components/automation/numeric_state.py | 4 ++-- .../components/automation/test_numeric_state.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 10a14b179a8..a4cbce8182a 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -41,7 +41,7 @@ def trigger(hass, config, action): if value_template is not None: renderer = lambda value: template.render(hass, value_template, - {'value': value}) + {'state': value}) else: renderer = lambda value: value.state @@ -82,7 +82,7 @@ def if_action(hass, config): if value_template is not None: renderer = lambda value: template.render(hass, value_template, - {'value': value}) + {'state': value}) else: renderer = lambda value: value.state diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 9e0301168f2..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', - 'value_template': '{{ value.attributes.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', - 'value_template': '{{ value.attributes.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', - 'value_template': '{{ value.attributes.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', - 'value_template': '{{ value.attributes.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', - 'value_template': '{{ value.attributes.test_attribute }}', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': { @@ -390,7 +390,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'value_template': '{{ value.attributes.test_attribute[2] }}', + 'value_template': '{{ state.attributes.test_attribute[2] }}', 'below': 10, }, 'action': { @@ -409,7 +409,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'value_template': '{{ value.attributes.test_attribute | multiply(10) }}', + 'value_template': '{{ state.attributes.test_attribute | multiply(10) }}', 'below': 10, }, 'action': { @@ -428,7 +428,7 @@ class TestAutomationNumericState(unittest.TestCase): 'trigger': { 'platform': 'numeric_state', 'entity_id': 'test.entity', - 'value_template': '{{ value.attributes.test_attribute }}', + 'value_template': '{{ state.attributes.test_attribute }}', 'below': 10, }, 'action': {