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