Add for option for template triggers (#24330)
This commit is contained in:
parent
d858e1be05
commit
61dabae6ab
2 changed files with 133 additions and 12 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue