Automation: Add trigger context and expose to action
This commit is contained in:
parent
c4913a87e4
commit
4e568f8b99
20 changed files with 232 additions and 69 deletions
|
@ -122,12 +122,11 @@ def _setup_automation(hass, config_block, name, config):
|
|||
|
||||
def _get_action(hass, config, name):
|
||||
"""Return an action based on a configuration."""
|
||||
def action():
|
||||
def action(variables=None):
|
||||
"""Action to be executed."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
|
||||
|
||||
call_from_config(hass, config)
|
||||
call_from_config(hass, config, variables=variables)
|
||||
|
||||
return action
|
||||
|
||||
|
@ -159,24 +158,21 @@ def _process_if(hass, config, p_config, action):
|
|||
checks.append(check)
|
||||
|
||||
if cond_type == CONDITION_TYPE_AND:
|
||||
def if_action():
|
||||
def if_action(variables=None):
|
||||
"""AND all conditions."""
|
||||
if all(check() for check in checks):
|
||||
action()
|
||||
if all(check(variables) for check in checks):
|
||||
action(variables)
|
||||
else:
|
||||
def if_action():
|
||||
def if_action(variables=None):
|
||||
"""OR all conditions."""
|
||||
if any(check() for check in checks):
|
||||
action()
|
||||
if any(check(variables) for check in checks):
|
||||
action(variables)
|
||||
|
||||
return if_action
|
||||
|
||||
|
||||
def _process_trigger(hass, config, trigger_configs, name, action):
|
||||
"""Setup the triggers."""
|
||||
if isinstance(trigger_configs, dict):
|
||||
trigger_configs = [trigger_configs]
|
||||
|
||||
for conf in trigger_configs:
|
||||
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
|
||||
conf.get(CONF_PLATFORM))
|
||||
|
|
|
@ -26,7 +26,12 @@ def trigger(hass, config, action):
|
|||
"""Listen for events and calls the action when data matches."""
|
||||
if not event_data or all(val == event.data.get(key) for key, val
|
||||
in event_data.items()):
|
||||
action()
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event': event,
|
||||
},
|
||||
})
|
||||
|
||||
hass.bus.listen(event_type, handle_event)
|
||||
return True
|
||||
|
|
|
@ -30,7 +30,14 @@ def trigger(hass, config, action):
|
|||
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
||||
"""Listen for MQTT messages."""
|
||||
if payload is None or payload == msg_payload:
|
||||
action()
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'mqtt',
|
||||
'topic': msg_topic,
|
||||
'payload': msg_payload,
|
||||
'qos': qos,
|
||||
}
|
||||
})
|
||||
|
||||
mqtt.subscribe(hass, topic, mqtt_automation_listener)
|
||||
|
||||
|
|
|
@ -18,12 +18,15 @@ CONF_ABOVE = "above"
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _renderer(hass, value_template, state):
|
||||
def _renderer(hass, value_template, state, variables=None):
|
||||
"""Render the state value."""
|
||||
if value_template is None:
|
||||
return state.state
|
||||
|
||||
return template.render(hass, value_template, {'state': state})
|
||||
variables = dict(variables or {})
|
||||
variables['state'] = state
|
||||
|
||||
return template.render(hass, value_template, variables)
|
||||
|
||||
|
||||
def trigger(hass, config, action):
|
||||
|
@ -50,9 +53,27 @@ def trigger(hass, config, action):
|
|||
def state_automation_listener(entity, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
# Fire action if we go from outside range into range
|
||||
if _in_range(above, below, renderer(to_s)) and \
|
||||
(from_s is None or not _in_range(above, below, renderer(from_s))):
|
||||
action()
|
||||
if to_s is None:
|
||||
return
|
||||
|
||||
variables = {
|
||||
'trigger': {
|
||||
'platform': 'numeric_state',
|
||||
'entity_id': entity_id,
|
||||
'below': below,
|
||||
'above': above,
|
||||
}
|
||||
}
|
||||
to_s_value = renderer(to_s, variables)
|
||||
from_s_value = None if from_s is None else renderer(from_s, variables)
|
||||
if _in_range(above, below, to_s_value) and \
|
||||
(from_s is None or not _in_range(above, below, from_s_value)):
|
||||
variables['trigger']['from_state'] = from_s
|
||||
variables['trigger']['from_value'] = from_s_value
|
||||
variables['trigger']['to_state'] = to_s
|
||||
variables['trigger']['to_value'] = to_s_value
|
||||
|
||||
action(variables)
|
||||
|
||||
track_state_change(
|
||||
hass, entity_id, state_automation_listener)
|
||||
|
@ -80,7 +101,7 @@ def if_action(hass, config):
|
|||
|
||||
renderer = partial(_renderer, hass, value_template)
|
||||
|
||||
def if_numeric_state():
|
||||
def if_numeric_state(variables):
|
||||
"""Test numeric state condition."""
|
||||
state = hass.states.get(entity_id)
|
||||
return state is not None and _in_range(above, below, renderer(state))
|
||||
|
|
|
@ -73,29 +73,42 @@ def trigger(hass, config, action):
|
|||
|
||||
def state_automation_listener(entity, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
def call_action():
|
||||
"""Call action with right context."""
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'state',
|
||||
'entity_id': entity,
|
||||
'from_state': from_s,
|
||||
'to_state': to_s,
|
||||
'for': time_delta,
|
||||
}
|
||||
})
|
||||
|
||||
if time_delta is None:
|
||||
call_action()
|
||||
return
|
||||
|
||||
def state_for_listener(now):
|
||||
"""Fire on state changes after a delay and calls action."""
|
||||
hass.bus.remove_listener(
|
||||
EVENT_STATE_CHANGED, for_state_listener)
|
||||
action()
|
||||
EVENT_STATE_CHANGED, attached_state_for_cancel_listener)
|
||||
call_action()
|
||||
|
||||
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
|
||||
"""Fire on changes and cancel for listener if changed."""
|
||||
if inner_to_s == to_s:
|
||||
return
|
||||
hass.bus.remove_listener(EVENT_TIME_CHANGED, for_time_listener)
|
||||
hass.bus.remove_listener(
|
||||
EVENT_STATE_CHANGED, for_state_listener)
|
||||
hass.bus.remove_listener(EVENT_TIME_CHANGED,
|
||||
attached_state_for_listener)
|
||||
hass.bus.remove_listener(EVENT_STATE_CHANGED,
|
||||
attached_state_for_cancel_listener)
|
||||
|
||||
if time_delta is not None:
|
||||
target_tm = dt_util.utcnow() + time_delta
|
||||
for_time_listener = track_point_in_time(
|
||||
hass, state_for_listener, target_tm)
|
||||
for_state_listener = track_state_change(
|
||||
hass, entity_id, state_for_cancel_listener,
|
||||
MATCH_ALL, MATCH_ALL)
|
||||
else:
|
||||
action()
|
||||
attached_state_for_listener = track_point_in_time(
|
||||
hass, state_for_listener, dt_util.utcnow() + time_delta)
|
||||
|
||||
attached_state_for_cancel_listener = track_state_change(
|
||||
hass, entity_id, state_for_cancel_listener)
|
||||
|
||||
track_state_change(
|
||||
hass, entity_id, state_automation_listener, from_state, to_state)
|
||||
|
@ -109,7 +122,7 @@ def if_action(hass, config):
|
|||
state = config.get(CONF_STATE)
|
||||
time_delta = get_time_config(config)
|
||||
|
||||
def if_state():
|
||||
def if_state(variables):
|
||||
"""Test if condition."""
|
||||
is_state = hass.states.is_state(entity_id, state)
|
||||
return (time_delta is None and is_state or
|
||||
|
|
|
@ -55,11 +55,21 @@ def trigger(hass, config, action):
|
|||
event = config.get(CONF_EVENT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
|
||||
def call_action():
|
||||
"""Call action with right context."""
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'sun',
|
||||
'event': event,
|
||||
'offset': offset,
|
||||
},
|
||||
})
|
||||
|
||||
# Do something to call action
|
||||
if event == EVENT_SUNRISE:
|
||||
track_sunrise(hass, action, offset)
|
||||
track_sunrise(hass, call_action, offset)
|
||||
else:
|
||||
track_sunset(hass, action, offset)
|
||||
track_sunset(hass, call_action, offset)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -97,7 +107,7 @@ def if_action(hass, config):
|
|||
"""Return time after sunset."""
|
||||
return sun.next_setting(hass) + after_offset
|
||||
|
||||
def time_if():
|
||||
def time_if(variables):
|
||||
"""Validate time based if-condition."""
|
||||
now = dt_util.now()
|
||||
before = before_func()
|
||||
|
|
|
@ -9,9 +9,10 @@ import logging
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_VALUE_TEMPLATE, EVENT_STATE_CHANGED, CONF_PLATFORM)
|
||||
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
|
@ -30,7 +31,7 @@ def trigger(hass, config, action):
|
|||
# Local variable to keep track of if the action has already been triggered
|
||||
already_triggered = False
|
||||
|
||||
def event_listener(event):
|
||||
def state_changed_listener(entity_id, from_s, to_s):
|
||||
"""Listen for state changes and calls action."""
|
||||
nonlocal already_triggered
|
||||
template_result = _check_template(hass, value_template)
|
||||
|
@ -38,11 +39,18 @@ def trigger(hass, config, action):
|
|||
# Check to see if template returns true
|
||||
if template_result and not already_triggered:
|
||||
already_triggered = True
|
||||
action()
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'template',
|
||||
'entity_id': entity_id,
|
||||
'from_state': from_s,
|
||||
'to_state': to_s,
|
||||
},
|
||||
})
|
||||
elif not template_result:
|
||||
already_triggered = False
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, event_listener)
|
||||
track_state_change(hass, MATCH_ALL, state_changed_listener)
|
||||
return True
|
||||
|
||||
|
||||
|
@ -50,13 +58,14 @@ def if_action(hass, config):
|
|||
"""Wrap action method with state based condition."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
|
||||
return lambda: _check_template(hass, value_template)
|
||||
return lambda variables: _check_template(hass, value_template,
|
||||
variables=variables)
|
||||
|
||||
|
||||
def _check_template(hass, value_template):
|
||||
def _check_template(hass, value_template, variables=None):
|
||||
"""Check if result of template is true."""
|
||||
try:
|
||||
value = template.render(hass, value_template, {})
|
||||
value = template.render(hass, value_template, variables)
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
|
|
|
@ -41,7 +41,12 @@ def trigger(hass, config, action):
|
|||
|
||||
def time_automation_listener(now):
|
||||
"""Listen for time changes and calls action."""
|
||||
action()
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'time',
|
||||
'now': now,
|
||||
},
|
||||
})
|
||||
|
||||
track_time_change(hass, time_automation_listener,
|
||||
hour=hours, minute=minutes, second=seconds)
|
||||
|
@ -73,7 +78,7 @@ def if_action(hass, config):
|
|||
_error_time(after, CONF_AFTER)
|
||||
return None
|
||||
|
||||
def time_if():
|
||||
def time_if(variables):
|
||||
"""Validate time based if-condition."""
|
||||
now = dt_util.now()
|
||||
if before is not None and now > now.replace(hour=before.hour,
|
||||
|
|
|
@ -48,13 +48,22 @@ def trigger(hass, config, action):
|
|||
to_s.attributes.get(ATTR_LONGITUDE)):
|
||||
return
|
||||
|
||||
from_match = _in_zone(hass, zone_entity_id, from_s) if from_s else None
|
||||
to_match = _in_zone(hass, zone_entity_id, to_s)
|
||||
zone_state = hass.states.get(zone_entity_id)
|
||||
from_match = _in_zone(hass, zone_state, from_s) if from_s else None
|
||||
to_match = _in_zone(hass, zone_state, to_s)
|
||||
|
||||
# pylint: disable=too-many-boolean-expressions
|
||||
if event == EVENT_ENTER and not from_match and to_match or \
|
||||
event == EVENT_LEAVE and from_match and not to_match:
|
||||
action()
|
||||
action({
|
||||
'trigger': {
|
||||
'platform': 'zone',
|
||||
'entity_id': entity,
|
||||
'from_state': from_s,
|
||||
'to_state': to_s,
|
||||
'zone': zone_state,
|
||||
},
|
||||
})
|
||||
|
||||
track_state_change(
|
||||
hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL)
|
||||
|
@ -67,20 +76,20 @@ def if_action(hass, config):
|
|||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
zone_entity_id = config.get(CONF_ZONE)
|
||||
|
||||
def if_in_zone():
|
||||
def if_in_zone(variables):
|
||||
"""Test if condition."""
|
||||
return _in_zone(hass, zone_entity_id, hass.states.get(entity_id))
|
||||
zone_state = hass.states.get(zone_entity_id)
|
||||
return _in_zone(hass, zone_state, hass.states.get(entity_id))
|
||||
|
||||
return if_in_zone
|
||||
|
||||
|
||||
def _in_zone(hass, zone_entity_id, state):
|
||||
def _in_zone(hass, zone_state, state):
|
||||
"""Check if state is in zone."""
|
||||
if not state or None in (state.attributes.get(ATTR_LATITUDE),
|
||||
state.attributes.get(ATTR_LONGITUDE)):
|
||||
return False
|
||||
|
||||
zone_state = hass.states.get(zone_entity_id)
|
||||
return zone_state and zone.in_zone(
|
||||
zone_state, state.attributes.get(ATTR_LATITUDE),
|
||||
state.attributes.get(ATTR_LONGITUDE),
|
||||
|
|
|
@ -21,7 +21,9 @@ def track_state_change(hass, entity_ids, action, from_state=None,
|
|||
to_state = _process_match_param(to_state)
|
||||
|
||||
# Ensure it is a lowercase list with entity ids we want to match on
|
||||
if isinstance(entity_ids, str):
|
||||
if entity_ids == MATCH_ALL:
|
||||
pass
|
||||
elif isinstance(entity_ids, str):
|
||||
entity_ids = (entity_ids.lower(),)
|
||||
else:
|
||||
entity_ids = tuple(entity_id.lower() for entity_id in entity_ids)
|
||||
|
@ -29,7 +31,8 @@ def track_state_change(hass, entity_ids, action, from_state=None,
|
|||
@ft.wraps(action)
|
||||
def state_change_listener(event):
|
||||
"""The listener that listens for specific state changes."""
|
||||
if event.data['entity_id'] not in entity_ids:
|
||||
if entity_ids != MATCH_ALL and \
|
||||
event.data['entity_id'] not in entity_ids:
|
||||
return
|
||||
|
||||
if event.data['old_state'] is None:
|
||||
|
|
|
@ -51,7 +51,10 @@ class TestAutomation(unittest.TestCase):
|
|||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data': {'some': 'data'}
|
||||
'data_template': {
|
||||
'some': '{{ trigger.platform }} - '
|
||||
'{{ trigger.event.event_type }}'
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -59,7 +62,7 @@ class TestAutomation(unittest.TestCase):
|
|||
self.hass.bus.fire('test_event')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual('data', self.calls[0].data['some'])
|
||||
self.assertEqual('event - test_event', self.calls[0].data['some'])
|
||||
|
||||
def test_service_specify_entity_id(self):
|
||||
"""Test service data."""
|
||||
|
|
|
@ -35,14 +35,20 @@ class TestAutomationMQTT(unittest.TestCase):
|
|||
'topic': 'test-topic'
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some': '{{ trigger.platform }} - {{ trigger.topic }}'
|
||||
' - {{ trigger.payload }}'
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fire_mqtt_message(self.hass, 'test-topic', '')
|
||||
fire_mqtt_message(self.hass, 'test-topic', 'test_payload')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual('mqtt - test-topic - test_payload',
|
||||
self.calls[0].data['some'])
|
||||
|
||||
def test_if_fires_on_topic_and_payload_match(self):
|
||||
"""Test if message is fired on topic and payload match."""
|
||||
|
|
|
@ -437,15 +437,28 @@ class TestAutomationNumericState(unittest.TestCase):
|
|||
'below': 10,
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||
'platform', 'entity_id', 'below', 'above',
|
||||
'from_state.state', 'from_value',
|
||||
'to_state.state', 'to_value'))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
# 9 is below 10
|
||||
self.hass.states.set('test.entity', 'entity',
|
||||
self.hass.states.set('test.entity', 'test state 1',
|
||||
{'test_attribute': '1.2'})
|
||||
self.hass.pool.block_till_done()
|
||||
self.hass.states.set('test.entity', 'test state 2',
|
||||
{'test_attribute': '0.9'})
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual(
|
||||
'numeric_state - test.entity - 10 - None - test state 1 - 12.0 - '
|
||||
'test state 2 - 9.0',
|
||||
self.calls[0].data['some'])
|
||||
|
||||
def test_not_fires_on_attr_change_with_attr_not_below_multiple_attr(self):
|
||||
""""Test if not fired changed attributes."""
|
||||
|
|
|
@ -31,6 +31,9 @@ class TestAutomationState(unittest.TestCase):
|
|||
|
||||
def test_if_fires_on_entity_change(self):
|
||||
"""Test for firing on entity change."""
|
||||
self.hass.states.set('test.entity', 'hello')
|
||||
self.hass.pool.block_till_done()
|
||||
|
||||
assert _setup_component(self.hass, automation.DOMAIN, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
|
@ -38,7 +41,13 @@ class TestAutomationState(unittest.TestCase):
|
|||
'entity_id': 'test.entity',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||
'platform', 'entity_id',
|
||||
'from_state.state', 'to_state.state',
|
||||
'for'))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -46,6 +55,9 @@ class TestAutomationState(unittest.TestCase):
|
|||
self.hass.states.set('test.entity', 'world')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual(
|
||||
'state - test.entity - hello - world - None',
|
||||
self.calls[0].data['some'])
|
||||
|
||||
def test_if_fires_on_entity_change_with_from_filter(self):
|
||||
"""Test for firing on entity change with filter."""
|
||||
|
|
|
@ -105,6 +105,11 @@ class TestAutomationSun(unittest.TestCase):
|
|||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some':
|
||||
'{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||
'platform', 'event', 'offset'))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -112,6 +117,7 @@ class TestAutomationSun(unittest.TestCase):
|
|||
fire_time_changed(self.hass, trigger_time)
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual('sun - sunset - 0:30:00', self.calls[0].data['some'])
|
||||
|
||||
def test_sunrise_trigger_with_offset(self):
|
||||
"""Test the runrise trigger with offset."""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""The tests fr the Template automation."""
|
||||
"""The tests for the Template automation."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.bootstrap import _setup_component
|
||||
|
@ -226,7 +226,13 @@ class TestAutomationTemplate(unittest.TestCase):
|
|||
{%- endif -%}''',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some':
|
||||
'{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||
'platform', 'entity_id', 'from_state.state',
|
||||
'to_state.state'))
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -234,6 +240,9 @@ class TestAutomationTemplate(unittest.TestCase):
|
|||
self.hass.states.set('test.entity', 'world')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual(
|
||||
'template - test.entity - hello - world',
|
||||
self.calls[0].data['some'])
|
||||
|
||||
def test_if_fires_on_no_change_with_template_advanced(self):
|
||||
"""Test for firing on no change with template advanced."""
|
||||
|
|
|
@ -176,7 +176,11 @@ class TestAutomationTime(unittest.TestCase):
|
|||
'after': '5:00:00',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation'
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some': '{{ trigger.platform }} - '
|
||||
'{{ trigger.now.hour }}'
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -186,6 +190,7 @@ class TestAutomationTime(unittest.TestCase):
|
|||
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual('time - 5', self.calls[0].data['some'])
|
||||
|
||||
def test_if_not_working_if_no_values_in_conf_provided(self):
|
||||
"""Test for failure if no configuration."""
|
||||
|
|
|
@ -52,6 +52,13 @@ class TestAutomationZone(unittest.TestCase):
|
|||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'some': '{{ trigger.%s }}' % '}} - {{ trigger.'.join((
|
||||
'platform', 'entity_id',
|
||||
'from_state.state', 'to_state.state',
|
||||
'zone.name'))
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -63,6 +70,9 @@ class TestAutomationZone(unittest.TestCase):
|
|||
self.hass.pool.block_till_done()
|
||||
|
||||
self.assertEqual(1, len(self.calls))
|
||||
self.assertEqual(
|
||||
'zone - test.entity - hello - hello - test',
|
||||
self.calls[0].data['some'])
|
||||
|
||||
def test_if_not_fires_for_enter_on_zone_leave(self):
|
||||
"""Test for not firing on zone leave."""
|
||||
|
|
|
@ -7,6 +7,7 @@ from datetime import datetime, timedelta
|
|||
from astral import Astral
|
||||
|
||||
import homeassistant.core as ha
|
||||
from homeassistant.const import MATCH_ALL
|
||||
from homeassistant.helpers.event import (
|
||||
track_point_in_utc_time,
|
||||
track_point_in_time,
|
||||
|
@ -93,6 +94,7 @@ class TestEventHelpers(unittest.TestCase):
|
|||
# 2 lists to track how often our callbacks get called
|
||||
specific_runs = []
|
||||
wildcard_runs = []
|
||||
wildercard_runs = []
|
||||
|
||||
track_state_change(
|
||||
self.hass, 'light.Bowl', lambda a, b, c: specific_runs.append(1),
|
||||
|
@ -100,14 +102,18 @@ class TestEventHelpers(unittest.TestCase):
|
|||
|
||||
track_state_change(
|
||||
self.hass, 'light.Bowl',
|
||||
lambda _, old_s, new_s: wildcard_runs.append((old_s, new_s)),
|
||||
ha.MATCH_ALL, ha.MATCH_ALL)
|
||||
lambda _, old_s, new_s: wildcard_runs.append((old_s, new_s)))
|
||||
|
||||
track_state_change(
|
||||
self.hass, MATCH_ALL,
|
||||
lambda _, old_s, new_s: wildercard_runs.append((old_s, new_s)))
|
||||
|
||||
# Adding state to state machine
|
||||
self.hass.states.set("light.Bowl", "on")
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(specific_runs))
|
||||
self.assertEqual(1, len(wildcard_runs))
|
||||
self.assertEqual(1, len(wildercard_runs))
|
||||
self.assertIsNone(wildcard_runs[-1][0])
|
||||
self.assertIsNotNone(wildcard_runs[-1][1])
|
||||
|
||||
|
@ -116,31 +122,45 @@ class TestEventHelpers(unittest.TestCase):
|
|||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(0, len(specific_runs))
|
||||
self.assertEqual(1, len(wildcard_runs))
|
||||
self.assertEqual(1, len(wildercard_runs))
|
||||
|
||||
# State change off -> on
|
||||
self.hass.states.set('light.Bowl', 'off')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(2, len(wildcard_runs))
|
||||
self.assertEqual(2, len(wildercard_runs))
|
||||
|
||||
# State change off -> off
|
||||
self.hass.states.set('light.Bowl', 'off', {"some_attr": 1})
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(3, len(wildcard_runs))
|
||||
self.assertEqual(3, len(wildercard_runs))
|
||||
|
||||
# State change off -> on
|
||||
self.hass.states.set('light.Bowl', 'on')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(4, len(wildcard_runs))
|
||||
self.assertEqual(4, len(wildercard_runs))
|
||||
|
||||
self.hass.states.remove('light.bowl')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(5, len(wildcard_runs))
|
||||
self.assertEqual(5, len(wildercard_runs))
|
||||
self.assertIsNotNone(wildcard_runs[-1][0])
|
||||
self.assertIsNone(wildcard_runs[-1][1])
|
||||
self.assertIsNotNone(wildercard_runs[-1][0])
|
||||
self.assertIsNone(wildercard_runs[-1][1])
|
||||
|
||||
# Set state for different entity id
|
||||
self.hass.states.set('switch.kitchen', 'on')
|
||||
self.hass.pool.block_till_done()
|
||||
self.assertEqual(1, len(specific_runs))
|
||||
self.assertEqual(5, len(wildcard_runs))
|
||||
self.assertEqual(6, len(wildercard_runs))
|
||||
|
||||
def test_track_sunrise(self):
|
||||
"""Test track the sunrise."""
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import homeassistant.components # noqa - to prevent circular import
|
||||
# To prevent circular import when running just this file
|
||||
import homeassistant.components # noqa
|
||||
from homeassistant import core as ha, loader
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID
|
||||
from homeassistant.helpers import service
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue