From 4e568f8b99e45d1642887d4770708ea28a33aa18 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 21 Apr 2016 13:59:42 -0700 Subject: [PATCH] Automation: Add trigger context and expose to action --- .../components/automation/__init__.py | 20 ++++----- homeassistant/components/automation/event.py | 7 ++- homeassistant/components/automation/mqtt.py | 9 +++- .../components/automation/numeric_state.py | 33 +++++++++++--- homeassistant/components/automation/state.py | 43 ++++++++++++------- homeassistant/components/automation/sun.py | 16 +++++-- .../components/automation/template.py | 23 +++++++--- homeassistant/components/automation/time.py | 9 +++- homeassistant/components/automation/zone.py | 23 +++++++--- homeassistant/helpers/event.py | 7 ++- tests/components/automation/test_init.py | 7 ++- tests/components/automation/test_mqtt.py | 10 ++++- .../automation/test_numeric_state.py | 17 +++++++- tests/components/automation/test_state.py | 14 +++++- tests/components/automation/test_sun.py | 6 +++ tests/components/automation/test_template.py | 13 +++++- tests/components/automation/test_time.py | 7 ++- tests/components/automation/test_zone.py | 10 +++++ tests/helpers/test_event.py | 24 ++++++++++- tests/helpers/test_service.py | 3 +- 20 files changed, 232 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 7e13ae3ed75..8cbaf35a5c4 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -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)) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 80dd6c29f6b..46b5b4ef10d 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -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 diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index db0c1be7c2a..e4a6b221e04 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -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) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 74f5b3ba805..6ed2add0b25 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -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)) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 742e6195949..802debbe63e 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -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 diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 2a564a3b588..c9db88a83c2 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -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() diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 02e8f30d209..66c20518c7e 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -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"): diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index 761ad73b826..879b0e113d9 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -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, diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 66ea3c2d7c7..fd798f45549 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -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), diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index b6a08cc59d0..50a7b290cc8 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -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: diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 355865e9e9c..f6d33c18071 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -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.""" diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 0fd2a9aef06..29d55b424f2 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -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.""" diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index ee29c0fb56f..37df19e38ed 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -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.""" diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 2f688249834..4a6971124b6 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -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.""" diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 738c171ce6c..1975dc8da44 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -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.""" diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index bb46c7a262a..a643b731492 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -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.""" diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 36f22a00148..0b19e9389e2 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -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.""" diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 87a22243760..24980b466bf 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -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.""" diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 6d3a9cbb6a0..5d9f8d28e20 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -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.""" diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 59ba1781ab2..c863a46ad3b 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -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