mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Merge pull request #765 from philipbl/template_automation
Add template automation
This commit is contained in:
commit
9087e6a0d0
65
homeassistant/components/automation/template.py
Normal file
65
homeassistant/components/automation/template.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"""
|
||||||
|
homeassistant.components.automation.template
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Offers template automation rules.
|
||||||
|
|
||||||
|
For more details about this automation rule, please refer to the documentation
|
||||||
|
at https://home-assistant.io/components/automation/#template-trigger
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED
|
||||||
|
from homeassistant.exceptions import TemplateError
|
||||||
|
from homeassistant.util import template
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def trigger(hass, config, action):
|
||||||
|
""" Listen for state changes based on `config`. """
|
||||||
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
if value_template is None:
|
||||||
|
_LOGGER.error("Missing configuration key %s", CONF_VALUE_TEMPLATE)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Local variable to keep track of if the action has already been triggered
|
||||||
|
already_triggered = False
|
||||||
|
|
||||||
|
def event_listener(event):
|
||||||
|
""" Listens for state changes and calls action. """
|
||||||
|
nonlocal already_triggered
|
||||||
|
template_result = _check_template(hass, value_template)
|
||||||
|
|
||||||
|
# Check to see if template returns true
|
||||||
|
if template_result and not already_triggered:
|
||||||
|
already_triggered = True
|
||||||
|
action()
|
||||||
|
elif not template_result:
|
||||||
|
already_triggered = False
|
||||||
|
|
||||||
|
hass.bus.listen(EVENT_STATE_CHANGED, event_listener)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def if_action(hass, config):
|
||||||
|
""" Wraps action method with state based condition. """
|
||||||
|
|
||||||
|
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
if value_template is None:
|
||||||
|
_LOGGER.error("Missing configuration key %s", CONF_VALUE_TEMPLATE)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return lambda: _check_template(hass, value_template)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_template(hass, value_template):
|
||||||
|
""" Checks if result of template is true """
|
||||||
|
try:
|
||||||
|
value = template.render(hass, value_template, {})
|
||||||
|
except TemplateError:
|
||||||
|
_LOGGER.exception('Error parsing template')
|
||||||
|
return False
|
||||||
|
|
||||||
|
return value.lower() == 'true'
|
355
tests/components/automation/test_template.py
Normal file
355
tests/components/automation/test_template.py
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
"""
|
||||||
|
tests.components.automation.test_template
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests template automation.
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import homeassistant.core as ha
|
||||||
|
import homeassistant.components.automation as automation
|
||||||
|
|
||||||
|
|
||||||
|
class TestAutomationTemplate(unittest.TestCase):
|
||||||
|
""" Test the event automation. """
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
|
self.hass = ha.HomeAssistant()
|
||||||
|
self.hass.states.set('test.entity', 'hello')
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def record_call(service):
|
||||||
|
self.calls.append(service)
|
||||||
|
|
||||||
|
self.hass.services.register('test', 'automation', record_call)
|
||||||
|
|
||||||
|
def tearDown(self): # pylint: disable=invalid-name
|
||||||
|
""" Stop down stuff we started. """
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
def test_if_fires_on_change_bool(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ true }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_str(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': 'true',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_str_crazy(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': 'TrUE',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_on_change_bool(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ false }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_on_change_str(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': 'False',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_on_change_str_crazy(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': 'Anything other than "true" is false.',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_no_change(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ true }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'hello')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_two_change(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ true }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Trigger once
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
# Trigger again
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_with_template(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ is_state("test.entity", "world") }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_not_fires_on_change_with_template(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ is_state("test.entity", "hello") }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_with_template_advanced(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '''{%- if is_state("test.entity", "world") -%}
|
||||||
|
true
|
||||||
|
{%- else -%}
|
||||||
|
false
|
||||||
|
{%- endif -%}''',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_no_change_with_template_advanced(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '''{%- if is_state("test.entity", "world") -%}
|
||||||
|
true
|
||||||
|
{%- else -%}
|
||||||
|
false
|
||||||
|
{%- endif -%}''',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Different state
|
||||||
|
self.hass.states.set('test.entity', 'worldz')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
# Different state
|
||||||
|
self.hass.states.set('test.entity', 'hello')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_with_template_2(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ not is_state("test.entity", "world") }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'home')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'work')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'not_home')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'home')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(2, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_action(self):
|
||||||
|
automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': 'test_event',
|
||||||
|
},
|
||||||
|
'condition': [{
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ is_state("test.entity", "world") }}'
|
||||||
|
}],
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Condition is not true yet
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
# Change condition to true, but it shouldn't be triggered yet
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
# Condition is true and event is triggered
|
||||||
|
self.hass.bus.fire('test_event')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(1, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_with_bad_template(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ ',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
def test_if_fires_on_change_with_bad_template_2(self):
|
||||||
|
self.assertTrue(automation.setup(self.hass, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'template',
|
||||||
|
'value_template': '{{ xyz | round(0) }}',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.hass.states.set('test.entity', 'world')
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
self.assertEqual(0, len(self.calls))
|
Loading…
x
Reference in New Issue
Block a user