Add for option for template triggers (#24330)

This commit is contained in:
Phil Bruckner 2019-06-07 23:45:37 -05:00 committed by Paulus Schoutsen
parent d858e1be05
commit 61dabae6ab
2 changed files with 133 additions and 12 deletions

View file

@ -4,8 +4,10 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_FOR
from homeassistant.helpers.event import async_track_template from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_same_state, async_track_template)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -13,6 +15,7 @@ _LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'template', vol.Required(CONF_PLATFORM): 'template',
vol.Required(CONF_VALUE_TEMPLATE): cv.template, vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta),
}) })
@ -20,10 +23,17 @@ async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass value_template.hass = hass
time_delta = config.get(CONF_FOR)
unsub_track_same = None
@callback @callback
def template_listener(entity_id, from_s, to_s): def template_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action.""" """Listen for state changes and calls action."""
nonlocal unsub_track_same
@callback
def call_action():
"""Call action with right context."""
hass.async_run_job(action({ hass.async_run_job(action({
'trigger': { 'trigger': {
'platform': 'template', 'platform': 'template',
@ -33,4 +43,24 @@ async def async_trigger(hass, config, action, automation_info):
}, },
}, context=(to_s.context if to_s else None))) }, context=(to_s.context if to_s else None)))
return async_track_template(hass, value_template, template_listener) if not time_delta:
call_action()
return
unsub_track_same = async_track_same_state(
hass, time_delta, call_action,
lambda _, _2, _3: condition.async_template(hass, value_template),
value_template.extract_entities())
unsub = async_track_template(
hass, value_template, template_listener)
@callback
def async_remove():
"""Remove state listeners async."""
unsub()
if unsub_track_same:
# pylint: disable=not-callable
unsub_track_same()
return async_remove

View file

@ -1,11 +1,15 @@
"""The tests for the Template automation.""" """The tests for the Template automation."""
from datetime import timedelta
import pytest import pytest
from homeassistant.core import Context from homeassistant.core import Context
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
import homeassistant.components.automation as automation import homeassistant.components.automation as automation
from tests.common import (assert_setup_component, mock_component) from tests.common import (
async_fire_time_changed, assert_setup_component, mock_component)
from tests.components.automation import common from tests.components.automation import common
from tests.common import async_mock_service from tests.common import async_mock_service
@ -434,3 +438,90 @@ async def test_wait_template_with_trigger(hass, calls):
assert 1 == len(calls) assert 1 == len(calls)
assert 'template - test.entity - hello - world' == \ assert 'template - test.entity - hello - world' == \
calls[0].data['some'] calls[0].data['some']
async def test_if_fires_on_change_with_for(hass, calls):
"""Test for firing on change with for."""
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'template',
'value_template': "{{ is_state('test.entity', 'world') }}",
'for': {
'seconds': 5
},
},
'action': {
'service': 'test.automation'
}
}
})
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=10))
await hass.async_block_till_done()
assert 1 == len(calls)
async def test_if_not_fires_on_change_with_for(hass, calls):
"""Test for firing on change with for."""
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'template',
'value_template': "{{ is_state('test.entity', 'world') }}",
'for': {
'seconds': 5
},
},
'action': {
'service': 'test.automation'
}
}
})
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=4))
await hass.async_block_till_done()
assert 0 == len(calls)
hass.states.async_set('test.entity', 'hello')
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=6))
await hass.async_block_till_done()
assert 0 == len(calls)
async def test_if_not_fires_when_turned_off_with_for(hass, calls):
"""Test for firing on change with for."""
assert await async_setup_component(hass, automation.DOMAIN, {
automation.DOMAIN: {
'trigger': {
'platform': 'template',
'value_template': "{{ is_state('test.entity', 'world') }}",
'for': {
'seconds': 5
},
},
'action': {
'service': 'test.automation'
}
}
})
hass.states.async_set('test.entity', 'world')
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=4))
await hass.async_block_till_done()
assert 0 == len(calls)
await common.async_turn_off(hass)
await hass.async_block_till_done()
assert 0 == len(calls)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=6))
await hass.async_block_till_done()
assert 0 == len(calls)