From 2fe8b154f1ebe99b9920443a1674d07a5ec35a7e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 18:22:49 -0700 Subject: [PATCH 01/12] Fix state automation configuration --- homeassistant/components/automation/state.py | 7 +- tests/components/automation/test_state.py | 78 ++++++++++---------- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index d336fcaa3d7..081026bb08b 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -14,6 +14,7 @@ CONF_ENTITY_ID = "state_entity_id" CONF_FROM = "state_from" CONF_TO = "state_to" CONF_STATE = "state" +CONF_IF_ENTITY_ID = "entity_id" def trigger(hass, config, action): @@ -40,13 +41,13 @@ def trigger(hass, config, action): def if_action(hass, config, action): """ Wraps action method with state based condition. """ - entity_id = config.get(CONF_ENTITY_ID) + entity_id = config.get(CONF_IF_ENTITY_ID) state = config.get(CONF_STATE) if entity_id is None or state is None: logging.getLogger(__name__).error( - "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, - CONF_STATE) + "Missing if-condition configuration key %s or %s", + CONF_IF_ENTITY_ID, CONF_STATE) return action def state_if(): diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 9dcfa49d54c..de8794b3337 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -8,8 +8,6 @@ import unittest import homeassistant.core as ha import homeassistant.components.automation as automation -from homeassistant.components.automation import event, state -from homeassistant.const import CONF_PLATFORM class TestAutomationState(unittest.TestCase): @@ -32,17 +30,17 @@ class TestAutomationState(unittest.TestCase): def test_setup_fails_if_no_entity_id(self): self.assertFalse(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'execute_service': 'test.automation' } })) def test_if_fires_on_entity_change(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'execute_service': 'test.automation' } })) @@ -53,10 +51,10 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_from_filter(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - state.CONF_FROM: 'hello', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'state_from': 'hello', + 'execute_service': 'test.automation' } })) @@ -67,10 +65,10 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_to_filter(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - state.CONF_TO: 'world', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'state_to': 'world', + 'execute_service': 'test.automation' } })) @@ -81,11 +79,11 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change_with_both_filters(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - state.CONF_FROM: 'hello', - state.CONF_TO: 'world', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'state_from': 'hello', + 'state_to': 'world', + 'execute_service': 'test.automation' } })) @@ -96,11 +94,11 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_if_to_filter_not_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - state.CONF_FROM: 'hello', - state.CONF_TO: 'world', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'state_from': 'hello', + 'state_to': 'world', + 'execute_service': 'test.automation' } })) @@ -113,11 +111,11 @@ class TestAutomationState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.entity', - state.CONF_FROM: 'hello', - state.CONF_TO: 'world', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.entity', + 'state_from': 'hello', + 'state_to': 'world', + 'execute_service': 'test.automation' } })) @@ -128,9 +126,9 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_if_entity_not_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: 'test.another_entity', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'state', + 'state_entity_id': 'test.another_entity', + 'execute_service': 'test.automation' } })) @@ -143,13 +141,13 @@ class TestAutomationState(unittest.TestCase): test_state = 'new_state' automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: [{ - CONF_PLATFORM: 'state', - state.CONF_ENTITY_ID: entity_id, - state.CONF_STATE: test_state, + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation', + 'if': [{ + 'platform': 'state', + 'entity_id': entity_id, + 'state': test_state, }] } }) From fe2a9bb83e293612bbcf06ba62a7932e761cf14a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 20:46:57 -0700 Subject: [PATCH 02/12] Fix numeric state if --- homeassistant/components/automation/numeric_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 417ffffff7d..05a4f75a909 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -70,7 +70,7 @@ def if_action(hass, config, action): """ Execute action if state matches. """ state = hass.states.get(entity_id) - if state is None or _in_range(state.state, above, below): + if state is not None and _in_range(state.state, above, below): action() return state_if From 68c1dd7cd47c516df765a1742a62abca5c3d5f83 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 22:05:40 -0700 Subject: [PATCH 03/12] Refactor automation configuration --- .../components/automation/__init__.py | 125 ++++++--- homeassistant/components/automation/mqtt.py | 4 +- .../components/automation/numeric_state.py | 6 +- homeassistant/components/automation/state.py | 9 +- homeassistant/components/automation/time.py | 6 +- tests/components/automation/test_event.py | 77 ++++-- tests/components/automation/test_init.py | 134 ++++++++-- tests/components/automation/test_mqtt.py | 78 ++++-- .../automation/test_numeric_state.py | 186 +++++++------ tests/components/automation/test_state.py | 185 +++++++++++-- tests/components/automation/test_time.py | 249 +++++++++++++++++- 11 files changed, 836 insertions(+), 223 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index a89afeb8c21..f659a2bdaff 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -7,7 +7,6 @@ Allows to setup simple automation rules via the config file. import logging from homeassistant.bootstrap import prepare_setup_platform -from homeassistant.helpers import config_per_platform from homeassistant.util import split_entity_id from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM from homeassistant.components import logbook @@ -20,50 +19,45 @@ CONF_ALIAS = "alias" CONF_SERVICE = "execute_service" CONF_SERVICE_ENTITY_ID = "service_entity_id" CONF_SERVICE_DATA = "service_data" -CONF_IF = "if" + +CONF_CONDITION = "condition" +CONF_ACTION = 'action' +CONF_TRIGGER = "trigger" _LOGGER = logging.getLogger(__name__) def setup(hass, config): """ Sets up automation. """ - success = False + config_key = DOMAIN + found = 1 - for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER): - platform = prepare_setup_platform(hass, config, DOMAIN, p_type) + while config_key in config: + p_config = _migrate_old_config(config[config_key]) + found += 1 + config_key = "{} {}".format(DOMAIN, found) - if platform is None: - _LOGGER.error("Unknown automation platform specified: %s", p_type) - continue - - action = _get_action(hass, p_config) + name = p_config.get(CONF_ALIAS, config_key) + action = _get_action(hass, p_config.get(CONF_ACTION, {}), name) if action is None: - return + continue - if CONF_IF in p_config: - action = _process_if(hass, config, p_config[CONF_IF], action) + if CONF_CONDITION in p_config: + action = _process_if(hass, config, p_config[CONF_CONDITION], action) - if platform.trigger(hass, p_config, action): - _LOGGER.info( - "Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, "")) - success = True - else: - _LOGGER.error( - "Error setting up rule %s", p_config.get(CONF_ALIAS, "")) + _process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name, + action) - return success + return True -def _get_action(hass, config): +def _get_action(hass, config, name): """ Return an action based on a config. """ - name = config.get(CONF_ALIAS, 'Unnamed automation') - if CONF_SERVICE not in config: - _LOGGER.error('Error setting up %s, no action specified.', - name) - return + _LOGGER.error('Error setting up %s, no action specified.', name) + return None def action(): """ Action to be executed. """ @@ -71,7 +65,6 @@ def _get_action(hass, config): logbook.log_entry(hass, name, 'has been triggered', DOMAIN) domain, service = split_entity_id(config[CONF_SERVICE]) - service_data = config.get(CONF_SERVICE_DATA, {}) if not isinstance(service_data, dict): @@ -91,6 +84,37 @@ def _get_action(hass, config): return action +def _migrate_old_config(config): + """ Migrate old config to new. """ + if CONF_PLATFORM not in config: + return config + + _LOGGER.warning( + 'You are using an old configuration format. Please upgrade: ' + 'https://home-assistant.io/components/automation.html') + + new_conf = { + CONF_TRIGGER: dict(config), + CONF_CONDITION: config.get('if', []), + CONF_ACTION: dict(config), + } + + for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'), + ('trigger', 'mqtt_payload', 'payload'), + ('trigger', 'state_entity_id', 'entity_id'), + ('trigger', 'state_before', 'before'), + ('trigger', 'state_after', 'after'), + ('trigger', 'state_to', 'to'), + ('trigger', 'state_from', 'from'), + ('trigger', 'state_hours', 'hours'), + ('trigger', 'state_minutes', 'minutes'), + ('trigger', 'state_seconds', 'seconds')): + if key in new_conf[cat]: + new_conf[cat][new_key] = new_conf[cat].pop(key) + + return new_conf + + def _process_if(hass, config, if_configs, action): """ Processes if checks. """ @@ -98,19 +122,42 @@ def _process_if(hass, config, if_configs, action): if_configs = [if_configs] for if_config in if_configs: - p_type = if_config.get(CONF_PLATFORM) - if p_type is None: - _LOGGER.error("No platform defined found for if-statement %s", - if_config) - continue - - platform = prepare_setup_platform(hass, config, DOMAIN, p_type) - - if platform is None or not hasattr(platform, 'if_action'): - _LOGGER.error("Unsupported if-statement platform specified: %s", - p_type) + platform = _resolve_platform('condition', hass, config, + if_config.get(CONF_PLATFORM)) + if platform is None: continue action = platform.if_action(hass, if_config, action) return action + + +def _process_trigger(hass, config, trigger_configs, name, action): + """ Setup triggers. """ + if isinstance(trigger_configs, dict): + trigger_configs = [trigger_configs] + + for conf in trigger_configs: + platform = _resolve_platform('trigger', hass, config, + conf.get(CONF_PLATFORM)) + if platform is None: + continue + + if platform.trigger(hass, conf, action): + _LOGGER.info("Initialized rule %s", name) + else: + _LOGGER.error("Error setting up rule %s", name) + + +def _resolve_platform(requester, hass, config, platform): + """ Find automation platform. """ + if platform is None: + return None + platform = prepare_setup_platform(hass, config, DOMAIN, platform) + + if platform is None: + _LOGGER.error("Unknown automation platform specified for %s: %s", + requester, platform) + return None + + return platform diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 7004b919c72..3f85792f907 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -10,8 +10,8 @@ import homeassistant.components.mqtt as mqtt DEPENDENCIES = ['mqtt'] -CONF_TOPIC = 'mqtt_topic' -CONF_PAYLOAD = 'mqtt_payload' +CONF_TOPIC = 'topic' +CONF_PAYLOAD = 'payload' def trigger(hass, config, action): diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 05a4f75a909..95691d0ebcc 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -9,9 +9,9 @@ import logging from homeassistant.helpers.event import track_state_change -CONF_ENTITY_ID = "state_entity_id" -CONF_BELOW = "state_below" -CONF_ABOVE = "state_above" +CONF_ENTITY_ID = "entity_id" +CONF_BELOW = "below" +CONF_ABOVE = "above" _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 081026bb08b..6f5a64e1070 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -10,11 +10,10 @@ from homeassistant.helpers.event import track_state_change from homeassistant.const import MATCH_ALL -CONF_ENTITY_ID = "state_entity_id" -CONF_FROM = "state_from" -CONF_TO = "state_to" +CONF_ENTITY_ID = "entity_id" +CONF_FROM = "from" +CONF_TO = "to" CONF_STATE = "state" -CONF_IF_ENTITY_ID = "entity_id" def trigger(hass, config, action): @@ -41,7 +40,7 @@ def trigger(hass, config, action): def if_action(hass, config, action): """ Wraps action method with state based condition. """ - entity_id = config.get(CONF_IF_ENTITY_ID) + entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) if entity_id is None or state is None: diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index b97f3e2f7f5..b5bfcd274ee 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -10,9 +10,9 @@ from homeassistant.util import convert import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_time_change -CONF_HOURS = "time_hours" -CONF_MINUTES = "time_minutes" -CONF_SECONDS = "time_seconds" +CONF_HOURS = "hours" +CONF_MINUTES = "minutes" +CONF_SECONDS = "seconds" CONF_BEFORE = "before" CONF_AFTER = "after" CONF_WEEKDAY = "weekday" diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index a2c36283c9a..b0ea144ac49 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -8,8 +8,6 @@ import unittest import homeassistant.core as ha import homeassistant.components.automation as automation -import homeassistant.components.automation.event as event -from homeassistant.const import CONF_PLATFORM class TestAutomationEvent(unittest.TestCase): @@ -28,20 +26,57 @@ class TestAutomationEvent(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_fails_setup_if_no_event_type(self): - self.assertFalse(automation.setup(self.hass, { + def test_old_config_if_fires_on_event(self): + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation' } })) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_old_config_if_fires_on_event_with_data(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'}, + 'execute_service': 'test.automation' + } + })) + + self.hass.bus.fire('test_event', {'some_attr': 'some_value'}) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_old_config_if_not_fires_if_event_data_not_matches(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'}, + 'execute_service': 'test.automation' + } + })) + + self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + def test_if_fires_on_event(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'execute_service': 'test.automation', + } } })) @@ -52,10 +87,14 @@ class TestAutomationEvent(unittest.TestCase): def test_if_fires_on_event_with_data(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'} + }, + 'action': { + 'execute_service': 'test.automation', + } } })) @@ -66,10 +105,14 @@ class TestAutomationEvent(unittest.TestCase): def test_if_not_fires_if_event_data_not_matches(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + 'event_data': {'some_attr': 'some_value'} + }, + 'action': { + 'execute_service': 'test.automation', + } } })) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 507c37dc20a..e2477972ead 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -8,8 +8,7 @@ import unittest import homeassistant.core as ha import homeassistant.components.automation as automation -import homeassistant.components.automation.event as event -from homeassistant.const import CONF_PLATFORM, ATTR_ENTITY_ID +from homeassistant.const import ATTR_ENTITY_ID class TestAutomationEvent(unittest.TestCase): @@ -28,20 +27,13 @@ class TestAutomationEvent(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_setup_fails_if_unknown_platform(self): - self.assertFalse(automation.setup(self.hass, { - automation.DOMAIN: { - CONF_PLATFORM: 'i_do_not_exist' - } - })) - def test_service_data_not_a_dict(self): automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_SERVICE_DATA: 100 + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation', + 'service_data': 100 } }) @@ -49,13 +41,64 @@ class TestAutomationEvent(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + def test_old_config_service_specify_data(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation', + 'service_data': {'some': 'data'} + } + }) + + 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']) + + def test_old_config_service_specify_entity_id(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation', + 'service_entity_id': 'hello.world' + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual(['hello.world'], + self.calls[0].data.get(ATTR_ENTITY_ID)) + + def test_old_config_service_specify_entity_id_list(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'event', + 'event_type': 'test_event', + 'execute_service': 'test.automation', + 'service_entity_id': ['hello.world', 'hello.world2'] + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual(['hello.world', 'hello.world2'], + self.calls[0].data.get(ATTR_ENTITY_ID)) + def test_service_specify_data(self): automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_SERVICE_DATA: {'some': 'data'} + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'execute_service': 'test.automation', + 'service_data': {'some': 'data'} + } } }) @@ -67,29 +110,66 @@ class TestAutomationEvent(unittest.TestCase): def test_service_specify_entity_id(self): automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_SERVICE_ENTITY_ID: 'hello.world' + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'execute_service': 'test.automation', + 'service_entity_id': 'hello.world' + } } }) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID]) + self.assertEqual(['hello.world'], + self.calls[0].data.get(ATTR_ENTITY_ID)) def test_service_specify_entity_id_list(self): automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_SERVICE_ENTITY_ID: ['hello.world', 'hello.world2'] + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'execute_service': 'test.automation', + 'service_entity_id': ['hello.world', 'hello.world2'] + } } }) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data[ATTR_ENTITY_ID]) + self.assertEqual(['hello.world', 'hello.world2'], + self.calls[0].data.get(ATTR_ENTITY_ID)) + + def test_two_triggers(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + { + 'platform': 'state', + 'entity_id': 'test.entity', + } + ], + 'action': { + 'execute_service': 'test.automation', + 'service_entity_id': ['hello.world', 'hello.world2'] + } + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.hass.states.set('test.entity', 'hello') + self.hass.pool.block_till_done() + self.assertEqual(2, len(self.calls)) diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 9402b5300b6..eb6d6a51768 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -8,9 +8,6 @@ import unittest import homeassistant.core as ha import homeassistant.components.automation as automation -import homeassistant.components.automation.mqtt as mqtt -from homeassistant.const import CONF_PLATFORM - from tests.common import mock_mqtt_component, fire_mqtt_message @@ -31,20 +28,57 @@ class TestAutomationState(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_setup_fails_if_no_topic(self): - self.assertFalse(automation.setup(self.hass, { + def test_old_config_if_fires_on_topic_match(self): + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'mqtt', - automation.CONF_SERVICE: 'test.automation' + 'platform': 'mqtt', + 'mqtt_topic': 'test-topic', + 'execute_service': 'test.automation' } })) + fire_mqtt_message(self.hass, 'test-topic', '') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_old_config_if_fires_on_topic_and_payload_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'mqtt', + 'mqtt_topic': 'test-topic', + 'mqtt_payload': 'hello', + 'execute_service': 'test.automation' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', 'hello') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_old_config_if_not_fires_on_topic_but_no_payload_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'platform': 'mqtt', + 'mqtt_topic': 'test-topic', + 'mqtt_payload': 'hello', + 'execute_service': 'test.automation' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', 'no-hello') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + def test_if_fires_on_topic_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'mqtt', - mqtt.CONF_TOPIC: 'test-topic', - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic' + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -55,10 +89,14 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_topic_and_payload_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'mqtt', - mqtt.CONF_TOPIC: 'test-topic', - mqtt.CONF_PAYLOAD: 'hello', - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic', + 'payload': 'hello' + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -69,10 +107,14 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_on_topic_but_no_payload_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'mqtt', - mqtt.CONF_TOPIC: 'test-topic', - mqtt.CONF_PAYLOAD: 'hello', - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'mqtt', + 'topic': 'test-topic', + 'payload': 'hello' + }, + 'action': { + 'execute_service': 'test.automation' + } } })) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 19a0f183876..31987a67f2b 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -8,8 +8,6 @@ import unittest import homeassistant.core as ha import homeassistant.components.automation as automation -from homeassistant.components.automation import event, numeric_state -from homeassistant.const import CONF_PLATFORM class TestAutomationNumericState(unittest.TestCase): @@ -28,31 +26,17 @@ class TestAutomationNumericState(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_setup_fails_if_no_entity_id(self): - self.assertFalse(automation.setup(self.hass, { - automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' - } - })) - - def test_setup_fails_if_no_condition(self): - self.assertFalse(automation.setup(self.hass, { - automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - automation.CONF_SERVICE: 'test.automation' - } - })) - def test_if_fires_on_entity_change_below(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) # 9 is below 10 @@ -66,10 +50,14 @@ class TestAutomationNumericState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -78,17 +66,20 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_not_fires_on_entity_change_below_to_below(self): self.hass.states.set('test.entity', 9) self.hass.pool.block_till_done() self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -97,14 +88,17 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) - def test_if_fires_on_entity_change_above(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) # 11 is above 10 @@ -119,10 +113,14 @@ class TestAutomationNumericState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -131,7 +129,6 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_not_fires_on_entity_change_above_to_above(self): # set initial state self.hass.states.set('test.entity', 11) @@ -139,10 +136,14 @@ class TestAutomationNumericState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'above': 10, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -154,11 +155,15 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below_range(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 5, - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) # 9 is below 10 @@ -169,11 +174,15 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_fires_on_entity_change_below_above_range(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 5, - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) # 4 is below 5 @@ -187,11 +196,15 @@ class TestAutomationNumericState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 5, - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -206,11 +219,15 @@ class TestAutomationNumericState(unittest.TestCase): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.entity', - numeric_state.CONF_ABOVE: 5, - numeric_state.CONF_BELOW: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.entity', + 'below': 10, + 'above': 5, + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -222,10 +239,13 @@ class TestAutomationNumericState(unittest.TestCase): def test_if_not_fires_if_entity_not_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: 'test.another_entity', - numeric_state.CONF_ABOVE: 10, - automation.CONF_SERVICE: 'test.automation' + 'trigger': { + 'platform': 'numeric_state', + 'entity_id': 'test.another_entity', + }, + 'action': { + 'execute_service': 'test.automation' + } } })) @@ -238,19 +258,23 @@ class TestAutomationNumericState(unittest.TestCase): test_state = 10 automation.setup(self.hass, { automation.DOMAIN: { - CONF_PLATFORM: 'event', - event.CONF_EVENT_TYPE: 'test_event', - automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: [{ - CONF_PLATFORM: 'numeric_state', - numeric_state.CONF_ENTITY_ID: entity_id, - numeric_state.CONF_ABOVE: test_state, - numeric_state.CONF_BELOW: test_state + 2, - }] + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': { + 'platform': 'numeric_state', + 'entity_id': entity_id, + 'above': test_state, + 'below': test_state + 2 + }, + 'action': { + 'execute_service': 'test.automation' + } } }) - self.hass.states.set(entity_id, test_state ) + self.hass.states.set(entity_id, test_state) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index de8794b3337..5b67c385f9c 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -27,15 +27,7 @@ class TestAutomationState(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_setup_fails_if_no_entity_id(self): - self.assertFalse(automation.setup(self.hass, { - automation.DOMAIN: { - 'platform': 'state', - 'execute_service': 'test.automation' - } - })) - - def test_if_fires_on_entity_change(self): + def test_old_config_if_fires_on_entity_change(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -48,7 +40,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_on_entity_change_with_from_filter(self): + def test_old_config_if_fires_on_entity_change_with_from_filter(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -62,7 +54,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_on_entity_change_with_to_filter(self): + def test_old_config_if_fires_on_entity_change_with_to_filter(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -76,7 +68,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_on_entity_change_with_both_filters(self): + def test_old_config_if_fires_on_entity_change_with_both_filters(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -91,7 +83,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_not_fires_if_to_filter_not_match(self): + def test_old_config_if_not_fires_if_to_filter_not_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -106,7 +98,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) - def test_if_not_fires_if_from_filter_not_match(self): + def test_old_config_if_not_fires_if_from_filter_not_match(self): self.hass.states.set('test.entity', 'bye') self.assertTrue(automation.setup(self.hass, { @@ -123,7 +115,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) - def test_if_not_fires_if_entity_not_match(self): + def test_old_config_if_not_fires_if_entity_not_match(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { 'platform': 'state', @@ -136,7 +128,7 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) - def test_if_action(self): + def test_old_config_if_action(self): entity_id = 'domain.test_entity' test_state = 'new_state' automation.setup(self.hass, { @@ -163,3 +155,164 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_entity_change(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + }, + 'action': { + 'execute_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_entity_change_with_from_filter(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello' + }, + 'action': { + 'execute_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_entity_change_with_to_filter(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'to': 'world' + }, + 'action': { + 'execute_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_entity_change_with_both_filters(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'execute_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_if_to_filter_not_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + self.hass.states.set('test.entity', 'moon') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_if_not_fires_if_from_filter_not_match(self): + self.hass.states.set('test.entity', 'bye') + + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'from': 'hello', + 'to': 'world' + }, + 'action': { + 'execute_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_if_entity_not_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.anoter_entity', + }, + 'action': { + 'execute_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_action(self): + entity_id = 'domain.test_entity' + test_state = 'new_state' + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'condition': [{ + 'platform': 'state', + 'entity_id': entity_id, + 'state': test_state + }], + 'action': { + 'execute_service': 'test.automation' + } + } + }) + + self.hass.states.set(entity_id, test_state) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, test_state + 'something') + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 48544bca25a..c135ba22223 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -33,7 +33,7 @@ class TestAutomationTime(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() - def test_if_fires_when_hour_matches(self): + def test_old_config_if_fires_when_hour_matches(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', @@ -48,7 +48,7 @@ class TestAutomationTime(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_when_minute_matches(self): + def test_old_config_if_fires_when_minute_matches(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', @@ -63,7 +63,7 @@ class TestAutomationTime(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_when_second_matches(self): + def test_old_config_if_fires_when_second_matches(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', @@ -78,7 +78,7 @@ class TestAutomationTime(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_fires_when_all_matches(self): + def test_old_config_if_fires_when_all_matches(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', @@ -96,13 +96,13 @@ class TestAutomationTime(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) - def test_if_action_before(self): + def test_old_config_if_action_before(self): automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: { + 'if': { CONF_PLATFORM: 'time', time.CONF_BEFORE: '10:00' } @@ -126,13 +126,13 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) - def test_if_action_after(self): + def test_old_config_if_action_after(self): automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: { + 'if': { CONF_PLATFORM: 'time', time.CONF_AFTER: '10:00' } @@ -156,13 +156,13 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) - def test_if_action_one_weekday(self): + def test_old_config_if_action_one_weekday(self): automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: { + 'if': { CONF_PLATFORM: 'time', time.CONF_WEEKDAY: 'mon', } @@ -187,13 +187,13 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) - def test_if_action_list_weekday(self): + def test_old_config_if_action_list_weekday(self): automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', automation.CONF_SERVICE: 'test.automation', - automation.CONF_IF: { + 'if': { CONF_PLATFORM: 'time', time.CONF_WEEKDAY: ['mon', 'tue'], } @@ -225,3 +225,228 @@ class TestAutomationTime(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(2, len(self.calls)) + + def test_if_fires_when_hour_matches(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'hours': 0, + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_minute_matches(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'minutes': 0, + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_second_matches(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'seconds': 0, + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_all_matches(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'hours': 0, + 'minutes': 0, + 'seconds': 0, + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + fire_time_changed(self.hass, dt_util.utcnow().replace( + hour=0, minute=0, second=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_action_before(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'platform': 'time', + 'before': '10:00', + }, + 'action': { + 'execute_service': 'test.automation' + } + } + }) + + before_10 = dt_util.now().replace(hour=8) + after_10 = dt_util.now().replace(hour=14) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=before_10): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=after_10): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + def test_if_action_after(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'platform': 'time', + 'after': '10:00', + }, + 'action': { + 'execute_service': 'test.automation' + } + } + }) + + before_10 = dt_util.now().replace(hour=8) + after_10 = dt_util.now().replace(hour=14) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=before_10): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(0, len(self.calls)) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=after_10): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + def test_if_action_one_weekday(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'platform': 'time', + 'weekday': 'mon', + }, + 'action': { + 'execute_service': 'test.automation' + } + } + }) + + days_past_monday = dt_util.now().weekday() + monday = dt_util.now() - timedelta(days=days_past_monday) + tuesday = monday + timedelta(days=1) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=monday): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=tuesday): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + def test_if_action_list_weekday(self): + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event' + }, + 'condition': { + 'platform': 'time', + 'weekday': ['mon', 'tue'], + }, + 'action': { + 'execute_service': 'test.automation' + } + } + }) + + days_past_monday = dt_util.now().weekday() + monday = dt_util.now() - timedelta(days=days_past_monday) + tuesday = monday + timedelta(days=1) + wednesday = tuesday + timedelta(days=1) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=monday): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=tuesday): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(2, len(self.calls)) + + with patch('homeassistant.components.automation.time.dt_util.now', + return_value=wednesday): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + + self.assertEqual(2, len(self.calls)) From fc43135ddd7eb224d0e2dccf5bcd1ad48c9aeee6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 22:12:51 -0700 Subject: [PATCH 04/12] Style fix --- homeassistant/components/automation/state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 6f5a64e1070..7bd0542855c 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -45,8 +45,8 @@ def if_action(hass, config, action): if entity_id is None or state is None: logging.getLogger(__name__).error( - "Missing if-condition configuration key %s or %s", - CONF_IF_ENTITY_ID, CONF_STATE) + "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, + CONF_STATE) return action def state_if(): From 20f021d05f419bf233dc152349e0f5b31d080cfe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 22:14:15 -0700 Subject: [PATCH 05/12] Another style fix. Who comes up with this? --- homeassistant/components/automation/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index f659a2bdaff..23126b2a3b3 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -44,7 +44,8 @@ def setup(hass, config): continue if CONF_CONDITION in p_config: - action = _process_if(hass, config, p_config[CONF_CONDITION], action) + action = _process_if(hass, config, p_config[CONF_CONDITION], + action) _process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name, action) From b2ad8db86bd67886f9bc4a049b49a5af5961f0cb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 22:51:28 -0700 Subject: [PATCH 06/12] Add condition type to automation component --- .../components/automation/__init__.py | 33 ++++++- .../components/automation/numeric_state.py | 16 ++-- homeassistant/components/automation/state.py | 15 ++-- homeassistant/components/automation/time.py | 11 +-- tests/components/automation/test_init.py | 88 ++++++++++++++++++- 5 files changed, 137 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 23126b2a3b3..73411ddd1db 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -23,6 +23,11 @@ CONF_SERVICE_DATA = "service_data" CONF_CONDITION = "condition" CONF_ACTION = 'action' CONF_TRIGGER = "trigger" +CONF_CONDITION_TYPE = "condition_type" + +CONDITION_TYPE_AND = "and" +CONDITION_TYPE_OR = "or" +DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND _LOGGER = logging.getLogger(__name__) @@ -44,8 +49,13 @@ def setup(hass, config): continue if CONF_CONDITION in p_config: + cond_type = p_config.get(CONF_CONDITION_TYPE, + DEFAULT_CONDITION_TYPE).lower() action = _process_if(hass, config, p_config[CONF_CONDITION], - action) + action, cond_type) + + if action is None: + continue _process_trigger(hass, config, p_config.get(CONF_TRIGGER, []), name, action) @@ -116,21 +126,36 @@ def _migrate_old_config(config): return new_conf -def _process_if(hass, config, if_configs, action): +def _process_if(hass, config, if_configs, action, cond_type): """ Processes if checks. """ if isinstance(if_configs, dict): if_configs = [if_configs] + checks = [] for if_config in if_configs: platform = _resolve_platform('condition', hass, config, if_config.get(CONF_PLATFORM)) if platform is None: continue - action = platform.if_action(hass, if_config, action) + check = platform.if_action(hass, if_config) - return action + if check is None: + return None + + checks.append(check) + + if cond_type == CONDITION_TYPE_AND: + def if_action(): + if all(check() for check in checks): + action() + else: + def if_action(): + if any(check() for check in checks): + action() + + return if_action def _process_trigger(hass, config, trigger_configs, name, action): diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 95691d0ebcc..7e014213d62 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -48,14 +48,14 @@ def trigger(hass, config, action): return True -def if_action(hass, config, action): +def if_action(hass, config): """ Wraps action method with state based condition. """ entity_id = config.get(CONF_ENTITY_ID) if entity_id is None: _LOGGER.error("Missing configuration key %s", CONF_ENTITY_ID) - return action + return None below = config.get(CONF_BELOW) above = config.get(CONF_ABOVE) @@ -64,16 +64,14 @@ def if_action(hass, config, action): _LOGGER.error("Missing configuration key." " One of %s or %s is required", CONF_BELOW, CONF_ABOVE) - return action - - def state_if(): - """ Execute action if state matches. """ + return None + def if_numeric_state(): + """ Test numeric state condition. """ state = hass.states.get(entity_id) - if state is not None and _in_range(state.state, above, below): - action() + return state is not None and _in_range(state.state, above, below) - return state_if + return if_numeric_state def _in_range(value, range_start, range_end): diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 7bd0542855c..bb936d36a1b 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -38,7 +38,7 @@ def trigger(hass, config, action): return True -def if_action(hass, config, action): +def if_action(hass, config): """ Wraps action method with state based condition. """ entity_id = config.get(CONF_ENTITY_ID) state = config.get(CONF_STATE) @@ -47,11 +47,12 @@ def if_action(hass, config, action): logging.getLogger(__name__).error( "Missing if-condition configuration key %s or %s", CONF_ENTITY_ID, CONF_STATE) - return action + return None - def state_if(): - """ Execute action if state matches. """ - if hass.states.is_state(entity_id, state): - action() + state = str(state) - return state_if + def if_state(): + """ Test if condition. """ + return hass.states.is_state(entity_id, state) + + return if_state diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index b5bfcd274ee..a7afa183ba0 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -36,7 +36,7 @@ def trigger(hass, config, action): return True -def if_action(hass, config, action): +def if_action(hass, config): """ Wraps action method with time based condition. """ before = config.get(CONF_BEFORE) after = config.get(CONF_AFTER) @@ -46,6 +46,7 @@ def if_action(hass, config, action): logging.getLogger(__name__).error( "Missing if-condition configuration key %s, %s or %s", CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY) + return None def time_if(): """ Validate time based if-condition """ @@ -59,7 +60,7 @@ def if_action(hass, config, action): minute=int(before_m)) if now > before_point: - return + return False if after is not None: # Strip seconds if given @@ -68,15 +69,15 @@ def if_action(hass, config, action): after_point = now.replace(hour=int(after_h), minute=int(after_m)) if now < after_point: - return + return False if weekday is not None: now_weekday = WEEKDAYS[now.weekday()] if isinstance(weekday, str) and weekday != now_weekday or \ now_weekday not in weekday: - return + return False - action() + return True return time_if diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index e2477972ead..8553a4472be 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -162,7 +162,6 @@ class TestAutomationEvent(unittest.TestCase): ], 'action': { 'execute_service': 'test.automation', - 'service_entity_id': ['hello.world', 'hello.world2'] } } }) @@ -173,3 +172,90 @@ class TestAutomationEvent(unittest.TestCase): self.hass.states.set('test.entity', 'hello') self.hass.pool.block_till_done() self.assertEqual(2, len(self.calls)) + + def test_two_conditions_with_and(self): + entity_id = 'test.entity' + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + ], + 'condition': [ + { + 'platform': 'state', + 'entity_id': entity_id, + 'state': 100 + }, + { + 'platform': 'numeric_state', + 'entity_id': entity_id, + 'below': 150 + } + ], + 'action': { + 'execute_service': 'test.automation', + } + } + }) + + self.hass.states.set(entity_id, 100) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, 101) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, 151) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_two_conditions_with_or(self): + entity_id = 'test.entity' + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + ], + 'condition_type': 'OR', + 'condition': [ + { + 'platform': 'state', + 'entity_id': entity_id, + 'state': 200 + }, + { + 'platform': 'numeric_state', + 'entity_id': entity_id, + 'below': 150 + } + ], + 'action': { + 'execute_service': 'test.automation', + } + } + }) + + self.hass.states.set(entity_id, 200) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, 100) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(2, len(self.calls)) + + self.hass.states.set(entity_id, 250) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(2, len(self.calls)) From e26f0f7b7d98ce53fa1e850270b89c446b96089c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 00:02:46 -0700 Subject: [PATCH 07/12] Update stale header doc --- tests/components/automation/test_event.py | 6 +++--- tests/components/automation/test_init.py | 7 +++---- tests/components/automation/test_mqtt.py | 6 +++--- tests/components/automation/test_numeric_state.py | 6 +++--- tests/components/automation/test_state.py | 6 +++--- tests/components/automation/test_time.py | 6 +++--- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index b0ea144ac49..01867f3850e 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,8 +1,8 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests event automation. """ import unittest diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 8553a4472be..df8b199b700 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,8 +1,7 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tests demo component. +tests.components.automation.test_init +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Tests automation component. """ import unittest diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index eb6d6a51768..d5e969abe5d 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -1,8 +1,8 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests mqtt automation. """ import unittest diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 31987a67f2b..e946c138a95 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -1,8 +1,8 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_numeric_state +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests numeric state automation. """ import unittest diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 5b67c385f9c..991c3e066d4 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -1,8 +1,8 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_state +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests state automation. """ import unittest diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index c135ba22223..96579af9aa8 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -1,8 +1,8 @@ """ -tests.test_component_demo -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +tests.components.automation.test_time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Tests demo component. +Tests time automation. """ from datetime import timedelta import unittest From 2978e0dabebfea1509eb7f8d678d3f50ceaf6139 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 00:02:54 -0700 Subject: [PATCH 08/12] Add sun automation trigger --- homeassistant/components/automation/sun.py | 103 +++++++++++++++++ tests/components/automation/test_sun.py | 128 +++++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 homeassistant/components/automation/sun.py create mode 100644 tests/components/automation/test_sun.py diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py new file mode 100644 index 00000000000..103df6c9b39 --- /dev/null +++ b/homeassistant/components/automation/sun.py @@ -0,0 +1,103 @@ +""" +homeassistant.components.automation.sun +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Offers sun based automation rules. +""" +import logging +from datetime import timedelta + +from homeassistant.components import sun +from homeassistant.helpers.event import track_point_in_utc_time +import homeassistant.util.dt as dt_util + +DEPENDENCIES = ['sun'] + +CONF_OFFSET = 'offset' +CONF_EVENT = 'event' + +EVENT_SUNSET = 'sunset' +EVENT_SUNRISE = 'sunrise' + +_LOGGER = logging.getLogger(__name__) + + +def trigger(hass, config, action): + """ Listen for events based on config. """ + event = config.get(CONF_EVENT) + + if event is None: + _LOGGER.error("Missing configuration key %s", CONF_EVENT) + return False + + event = event.lower() + if event not in (EVENT_SUNRISE, EVENT_SUNSET): + _LOGGER.error("Invalid value for %s: %s", CONF_EVENT, event) + return False + + if CONF_OFFSET in config: + raw_offset = config.get(CONF_OFFSET) + + negative_offset = False + if raw_offset.startswith('-'): + negative_offset = True + raw_offset = raw_offset[1:] + + try: + (hour, minute, second) = [int(x) for x in raw_offset.split(':')] + except ValueError: + _LOGGER.error('Could not parse offset %s', raw_offset) + return False + + offset = timedelta(hours=hour, minutes=minute, seconds=second) + + if negative_offset: + offset *= -1 + else: + offset = timedelta(0) + + # Do something to call action + if event == EVENT_SUNRISE: + trigger_sunrise(hass, action, offset) + else: + trigger_sunset(hass, action, offset) + + return True + + +def trigger_sunrise(hass, action, offset): + """ Trigger action at next sun rise. """ + def next_rise(): + """ Returns next sunrise. """ + next_time = sun.next_rising_utc(hass) + offset + + while next_time < dt_util.utcnow(): + next_time = next_time + timedelta(days=1) + + return next_time + + def sunrise_automation_listener(now): + """ Called when it's time for action. """ + track_point_in_utc_time(hass, sunrise_automation_listener, next_rise()) + action() + + track_point_in_utc_time(hass, sunrise_automation_listener, next_rise()) + + +def trigger_sunset(hass, action, offset): + """ Trigger action at next sun set. """ + def next_set(): + """ Returns next sunrise. """ + next_time = sun.next_setting_utc(hass) + offset + + while next_time < dt_util.utcnow(): + next_time = next_time + timedelta(days=1) + + return next_time + + def sunset_automation_listener(now): + """ Called when it's time for action. """ + track_point_in_utc_time(hass, sunset_automation_listener, next_set()) + action() + + track_point_in_utc_time(hass, sunset_automation_listener, next_set()) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py new file mode 100644 index 00000000000..c2b292ea0e5 --- /dev/null +++ b/tests/components/automation/test_sun.py @@ -0,0 +1,128 @@ +""" +tests.components.automation.test_sun +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests sun automation. +""" +from datetime import datetime +import unittest + +import homeassistant.core as ha +from homeassistant.components import sun +import homeassistant.components.automation as automation +import homeassistant.util.dt as dt_util + +from tests.common import fire_time_changed + + +class TestAutomationSun(unittest.TestCase): + """ Test the sun automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + self.hass.config.components.append('sun') + + 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_sunset_trigger(self): + self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { + sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015', + }) + + trigger_time = datetime(2015, 9, 16, 2, tzinfo=dt_util.UTC) + + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + }, + 'action': { + 'execute_service': 'test.automation', + } + } + })) + + fire_time_changed(self.hass, trigger_time) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_sunrise_trigger(self): + self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { + sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + }) + + trigger_time = datetime(2015, 9, 16, 14, tzinfo=dt_util.UTC) + + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + }, + 'action': { + 'execute_service': 'test.automation', + } + } + })) + + fire_time_changed(self.hass, trigger_time) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_sunset_trigger_with_offset(self): + self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { + sun.STATE_ATTR_NEXT_SETTING: '02:00:00 16-09-2015', + }) + + trigger_time = datetime(2015, 9, 16, 2, 30, tzinfo=dt_util.UTC) + + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + 'offset': '0:30:00' + }, + 'action': { + 'execute_service': 'test.automation', + } + } + })) + + fire_time_changed(self.hass, trigger_time) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_sunrise_trigger_with_offset(self): + self.hass.states.set(sun.ENTITY_ID, sun.STATE_ABOVE_HORIZON, { + sun.STATE_ATTR_NEXT_RISING: '14:00:00 16-09-2015', + }) + + trigger_time = datetime(2015, 9, 16, 13, 30, tzinfo=dt_util.UTC) + + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'sun', + 'event': 'sunset', + 'offset': '-0:30:00' + }, + 'action': { + 'execute_service': 'test.automation', + } + } + })) + + fire_time_changed(self.hass, trigger_time) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) From 1ec5178f66e3491d9eec705877a1c8805acecceb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 00:05:20 -0700 Subject: [PATCH 09/12] Remove scheduler component --- .../components/scheduler/__init__.py | 137 ------------------ homeassistant/components/scheduler/time.py | 70 --------- homeassistant/components/sun.py | 96 +----------- 3 files changed, 1 insertion(+), 302 deletions(-) delete mode 100644 homeassistant/components/scheduler/__init__.py delete mode 100644 homeassistant/components/scheduler/time.py diff --git a/homeassistant/components/scheduler/__init__.py b/homeassistant/components/scheduler/__init__.py deleted file mode 100644 index 1a67636da3d..00000000000 --- a/homeassistant/components/scheduler/__init__.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -homeassistant.components.scheduler -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A component that will act as a scheduler and perform actions based -on the events in the schedule. - -It will read a json object from schedule.json in the config dir -and create a schedule based on it. -Each schedule is a JSON with the keys id, name, description, -entity_ids, and events. -- days is an array with the weekday number (monday=0) that the schedule - is active -- entity_ids an array with entity ids that the events in the schedule should - effect (can also be groups) -- events is an array of objects that describe the different events that is - supported. Read in the events descriptions for more information. -""" -import logging -import json - -from homeassistant import bootstrap -from homeassistant.loader import get_component -from homeassistant.const import ATTR_ENTITY_ID - -DOMAIN = 'scheduler' - -DEPENDENCIES = [] - -_LOGGER = logging.getLogger(__name__) - -_SCHEDULE_FILE = 'schedule.json' - - -def setup(hass, config): - """ Create the schedules. """ - - def setup_listener(schedule, event_data): - """ Creates the event listener based on event_data. """ - event_type = event_data['type'] - component = event_type - - # if the event isn't part of a component - if event_type in ['time']: - component = 'scheduler.{}'.format(event_type) - - elif not bootstrap.setup_component(hass, component, config): - _LOGGER.warn("Could setup event listener for %s", component) - return None - - return get_component(component).create_event_listener(schedule, - event_data) - - def setup_schedule(schedule_data): - """ Setup a schedule based on the description. """ - - schedule = Schedule(schedule_data['id'], - name=schedule_data['name'], - description=schedule_data['description'], - entity_ids=schedule_data['entity_ids'], - days=schedule_data['days']) - - for event_data in schedule_data['events']: - event_listener = setup_listener(schedule, event_data) - - if event_listener: - schedule.add_event_listener(event_listener) - - schedule.schedule(hass) - return True - - with open(hass.config.path(_SCHEDULE_FILE)) as schedule_file: - schedule_descriptions = json.load(schedule_file) - - for schedule_description in schedule_descriptions: - if not setup_schedule(schedule_description): - return False - - return True - - -class Schedule(object): - """ A Schedule """ - - # pylint: disable=too-many-arguments - def __init__(self, schedule_id, name=None, description=None, - entity_ids=None, days=None): - - self.schedule_id = schedule_id - self.name = name - self.description = description - - self.entity_ids = entity_ids or [] - - self.days = days or [0, 1, 2, 3, 4, 5, 6] - - self.__event_listeners = [] - - def add_event_listener(self, event_listener): - """ Add a event to the schedule. """ - self.__event_listeners.append(event_listener) - - def schedule(self, hass): - """ Schedule all the events in the schedule. """ - for event in self.__event_listeners: - event.schedule(hass) - - -class EventListener(object): - """ The base EventListener class that the schedule uses. """ - def __init__(self, schedule): - self.my_schedule = schedule - - def schedule(self, hass): - """ Schedule the event """ - pass - - def execute(self, hass): - """ execute the event """ - pass - - -# pylint: disable=too-few-public-methods -class ServiceEventListener(EventListener): - """ A EventListener that calls a service when executed. """ - - def __init__(self, schdule, service): - EventListener.__init__(self, schdule) - - (self.domain, self.service) = service.split('.') - - def execute(self, hass): - """ Call the service. """ - data = {ATTR_ENTITY_ID: self.my_schedule.entity_ids} - hass.services.call(self.domain, self.service, data) - - # Reschedule for next day - self.schedule(hass) diff --git a/homeassistant/components/scheduler/time.py b/homeassistant/components/scheduler/time.py deleted file mode 100644 index 4d0280dfdf9..00000000000 --- a/homeassistant/components/scheduler/time.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -homeassistant.components.scheduler.time -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -An event in the scheduler component that will call the service -every specified day at the time specified. -A time event need to have the type 'time', which service to call and at -which time. - -{ - "type": "time", - "service": "switch.turn_off", - "time": "22:00:00" -} - -""" -from datetime import timedelta -import logging - -import homeassistant.util.dt as dt_util -from homeassistant.helpers.event import track_point_in_time -from homeassistant.components.scheduler import ServiceEventListener - -_LOGGER = logging.getLogger(__name__) - - -def create_event_listener(schedule, event_listener_data): - """ Create a TimeEvent based on the description. """ - - service = event_listener_data['service'] - (hour, minute, second) = [int(x) for x in - event_listener_data['time'].split(':', 3)] - - return TimeEventListener(schedule, service, hour, minute, second) - - -# pylint: disable=too-few-public-methods -class TimeEventListener(ServiceEventListener): - """ The time event that the scheduler uses. """ - - # pylint: disable=too-many-arguments - def __init__(self, schedule, service, hour, minute, second): - ServiceEventListener.__init__(self, schedule, service) - - self.hour = hour - self.minute = minute - self.second = second - - def schedule(self, hass): - """ Schedule this event so that it will be called. """ - - next_time = dt_util.now().replace( - hour=self.hour, minute=self.minute, second=self.second) - - # Calculate the next time the event should be executed. - # That is the next day that the schedule is configured to run - while next_time < dt_util.now() or \ - next_time.weekday() not in self.my_schedule.days: - - next_time = next_time + timedelta(days=1) - - # pylint: disable=unused-argument - def execute(now): - """ Call the execute method """ - self.execute(hass) - - track_point_in_time(hass, execute, next_time) - - _LOGGER.info( - 'TimeEventListener scheduled for %s, will call service %s.%s', - next_time, self.domain, self.service) diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py index 802eddb4a3a..ce4dbd1e937 100644 --- a/homeassistant/components/sun.py +++ b/homeassistant/components/sun.py @@ -25,10 +25,8 @@ import urllib import homeassistant.util as util import homeassistant.util.dt as dt_util -from homeassistant.helpers.event import ( - track_point_in_utc_time, track_point_in_time) +from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.entity import Entity -from homeassistant.components.scheduler import ServiceEventListener DEPENDENCIES = [] REQUIREMENTS = ['astral==0.8.1'] @@ -214,95 +212,3 @@ class Sun(Entity): track_point_in_utc_time( self.hass, self.point_in_time_listener, self.next_change + timedelta(seconds=1)) - - -def create_event_listener(schedule, event_listener_data): - """ Create a sun event listener based on the description. """ - - negative_offset = False - service = event_listener_data['service'] - offset_str = event_listener_data['offset'] - event = event_listener_data['event'] - - if offset_str.startswith('-'): - negative_offset = True - offset_str = offset_str[1:] - - (hour, minute, second) = [int(x) for x in offset_str.split(':')] - - offset = timedelta(hours=hour, minutes=minute, seconds=second) - - if event == 'sunset': - return SunsetEventListener(schedule, service, offset, negative_offset) - - return SunriseEventListener(schedule, service, offset, negative_offset) - - -# pylint: disable=too-few-public-methods -class SunEventListener(ServiceEventListener): - """ This is the base class for sun event listeners. """ - - def __init__(self, schedule, service, offset, negative_offset): - ServiceEventListener.__init__(self, schedule, service) - - self.offset = offset - self.negative_offset = negative_offset - - def __get_next_time(self, next_event): - """ - Returns when the next time the service should be called. - Taking into account the offset and which days the event should execute. - """ - - if self.negative_offset: - next_time = next_event - self.offset - else: - next_time = next_event + self.offset - - while next_time < dt_util.now() or \ - next_time.weekday() not in self.my_schedule.days: - next_time = next_time + timedelta(days=1) - - return next_time - - def schedule_next_event(self, hass, next_event): - """ Schedule the event. """ - next_time = self.__get_next_time(next_event) - - # pylint: disable=unused-argument - def execute(now): - """ Call the execute method. """ - self.execute(hass) - - track_point_in_time(hass, execute, next_time) - - return next_time - - -# pylint: disable=too-few-public-methods -class SunsetEventListener(SunEventListener): - """ This class is used the call a service when the sun sets. """ - def schedule(self, hass): - """ Schedule the event """ - next_setting_dt = next_setting(hass) - - next_time_dt = self.schedule_next_event(hass, next_setting_dt) - - _LOGGER.info( - 'SunsetEventListener scheduled for %s, will call service %s.%s', - next_time_dt, self.domain, self.service) - - -# pylint: disable=too-few-public-methods -class SunriseEventListener(SunEventListener): - """ This class is used the call a service when the sun rises. """ - - def schedule(self, hass): - """ Schedule the event. """ - next_rising_dt = next_rising(hass) - - next_time_dt = self.schedule_next_event(hass, next_rising_dt) - - _LOGGER.info( - 'SunriseEventListener scheduled for %s, will call service %s.%s', - next_time_dt, self.domain, self.service) From ae527e9c6f60ba5960f0216d35d63ae691ed11c6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 00:07:49 -0700 Subject: [PATCH 10/12] Fix broken sun automation test --- tests/components/automation/test_sun.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index c2b292ea0e5..dcb9cbafc1f 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -67,7 +67,7 @@ class TestAutomationSun(unittest.TestCase): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunset', + 'event': 'sunrise', }, 'action': { 'execute_service': 'test.automation', @@ -114,7 +114,7 @@ class TestAutomationSun(unittest.TestCase): automation.DOMAIN: { 'trigger': { 'platform': 'sun', - 'event': 'sunset', + 'event': 'sunrise', 'offset': '-0:30:00' }, 'action': { From 0584c10ef96f5a23d2b6d9ae34a6b6dc3b8ae1a2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 00:11:24 -0700 Subject: [PATCH 11/12] Style fix --- homeassistant/components/automation/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 73411ddd1db..0bb47a97a3c 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -148,10 +148,12 @@ def _process_if(hass, config, if_configs, action, cond_type): if cond_type == CONDITION_TYPE_AND: def if_action(): + """ AND all conditions. """ if all(check() for check in checks): action() else: def if_action(): + """ OR all conditions. """ if any(check() for check in checks): action() From c18294ee76ac6a6af233558acebeb8fc44c09b72 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Sep 2015 08:56:06 -0700 Subject: [PATCH 12/12] Allow triggers to be used as condition --- .../components/automation/__init__.py | 53 ++++++++------- homeassistant/components/automation/state.py | 2 +- homeassistant/components/automation/time.py | 24 ++++--- homeassistant/util/dt.py | 17 +++++ tests/components/automation/test_init.py | 64 +++++++++++++++++++ tests/components/automation/test_state.py | 18 ++++++ tests/components/automation/test_time.py | 23 +++++-- 7 files changed, 166 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 0bb47a97a3c..45859617624 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -11,22 +11,24 @@ from homeassistant.util import split_entity_id from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM from homeassistant.components import logbook -DOMAIN = "automation" +DOMAIN = 'automation' -DEPENDENCIES = ["group"] +DEPENDENCIES = ['group'] -CONF_ALIAS = "alias" -CONF_SERVICE = "execute_service" -CONF_SERVICE_ENTITY_ID = "service_entity_id" -CONF_SERVICE_DATA = "service_data" +CONF_ALIAS = 'alias' +CONF_SERVICE = 'execute_service' +CONF_SERVICE_ENTITY_ID = 'service_entity_id' +CONF_SERVICE_DATA = 'service_data' -CONF_CONDITION = "condition" +CONF_CONDITION = 'condition' CONF_ACTION = 'action' -CONF_TRIGGER = "trigger" -CONF_CONDITION_TYPE = "condition_type" +CONF_TRIGGER = 'trigger' +CONF_CONDITION_TYPE = 'condition_type' + +CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values' +CONDITION_TYPE_AND = 'and' +CONDITION_TYPE_OR = 'or' -CONDITION_TYPE_AND = "and" -CONDITION_TYPE_OR = "or" DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND _LOGGER = logging.getLogger(__name__) @@ -48,11 +50,8 @@ def setup(hass, config): if action is None: continue - if CONF_CONDITION in p_config: - cond_type = p_config.get(CONF_CONDITION_TYPE, - DEFAULT_CONDITION_TYPE).lower() - action = _process_if(hass, config, p_config[CONF_CONDITION], - action, cond_type) + if CONF_CONDITION in p_config or CONF_CONDITION_TYPE in p_config: + action = _process_if(hass, config, p_config, action) if action is None: continue @@ -126,22 +125,32 @@ def _migrate_old_config(config): return new_conf -def _process_if(hass, config, if_configs, action, cond_type): +def _process_if(hass, config, p_config, action): """ Processes if checks. """ + cond_type = p_config.get(CONF_CONDITION_TYPE, + DEFAULT_CONDITION_TYPE).lower() + + if_configs = p_config.get(CONF_CONDITION) + use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES + + if use_trigger: + if_configs = p_config[CONF_TRIGGER] + if isinstance(if_configs, dict): if_configs = [if_configs] checks = [] for if_config in if_configs: - platform = _resolve_platform('condition', hass, config, + platform = _resolve_platform('if_action', hass, config, if_config.get(CONF_PLATFORM)) if platform is None: continue check = platform.if_action(hass, if_config) - if check is None: + # Invalid conditions are allowed if we base it on trigger + if check is None and not use_trigger: return None checks.append(check) @@ -177,15 +186,15 @@ def _process_trigger(hass, config, trigger_configs, name, action): _LOGGER.error("Error setting up rule %s", name) -def _resolve_platform(requester, hass, config, platform): +def _resolve_platform(method, hass, config, platform): """ Find automation platform. """ if platform is None: return None platform = prepare_setup_platform(hass, config, DOMAIN, platform) - if platform is None: + if platform is None or not hasattr(platform, method): _LOGGER.error("Unknown automation platform specified for %s: %s", - requester, platform) + method, platform) return None return platform diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index bb936d36a1b..8baa0a01d46 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -26,7 +26,7 @@ def trigger(hass, config, action): return False from_state = config.get(CONF_FROM, MATCH_ALL) - to_state = config.get(CONF_TO, MATCH_ALL) + to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL def state_automation_listener(entity, from_s, to_s): """ Listens for state changes and calls action. """ diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index a7afa183ba0..821295fdffa 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -22,6 +22,14 @@ WEEKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'] def trigger(hass, config, action): """ Listen for state changes based on `config`. """ + if CONF_AFTER in config: + after = dt_util.parse_time_str(config[CONF_AFTER]) + if after is None: + logging.getLogger(__name__).error( + 'Received invalid after value: %s', config[CONF_AFTER]) + return False + hours, minutes, seconds = after.hour, after.minute, after.second + hours = convert(config.get(CONF_HOURS), int) minutes = convert(config.get(CONF_MINUTES), int) seconds = convert(config.get(CONF_SECONDS), int) @@ -51,22 +59,22 @@ def if_action(hass, config): def time_if(): """ Validate time based if-condition """ now = dt_util.now() - if before is not None: - # Strip seconds if given - before_h, before_m = before.split(':')[0:2] + time = dt_util.parse_time_str(before) + if time is None: + return False - before_point = now.replace(hour=int(before_h), - minute=int(before_m)) + before_point = now.replace(hour=time.hour, minute=time.minute) if now > before_point: return False if after is not None: - # Strip seconds if given - after_h, after_m = after.split(':')[0:2] + time = dt_util.parse_time_str(after) + if time is None: + return False - after_point = now.replace(hour=int(after_h), minute=int(after_m)) + after_point = now.replace(hour=time.hour, minute=time.minute) if now < after_point: return False diff --git a/homeassistant/util/dt.py b/homeassistant/util/dt.py index d8fecf20db8..35795a7ae7f 100644 --- a/homeassistant/util/dt.py +++ b/homeassistant/util/dt.py @@ -131,3 +131,20 @@ def date_str_to_date(dt_str): def strip_microseconds(dattim): """ Returns a copy of dattime object but with microsecond set to 0. """ return dattim.replace(microsecond=0) + + +def parse_time_str(time_str): + """ Parse a time string (00:20:00) into Time object. + Return None if invalid. + """ + parts = str(time_str).split(':') + if len(parts) < 2: + return None + try: + hour = int(parts[0]) + minute = int(parts[1]) + second = int(parts[2]) if len(parts) > 2 else 0 + return dt.time(hour, minute, second) + except ValueError: + # ValueError if value cannot be converted to an int or not in range + return None diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index df8b199b700..6a011a072a5 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -258,3 +258,67 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event') self.hass.pool.block_till_done() self.assertEqual(2, len(self.calls)) + + def test_using_trigger_as_condition(self): + """ """ + entity_id = 'test.entity' + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'state', + 'entity_id': entity_id, + 'state': 100 + }, + { + 'platform': 'numeric_state', + 'entity_id': entity_id, + 'below': 150 + } + ], + 'condition': 'use_trigger_values', + 'action': { + 'execute_service': 'test.automation', + } + } + }) + + self.hass.states.set(entity_id, 100) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, 120) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + self.hass.states.set(entity_id, 151) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_using_trigger_as_condition_with_invalid_condition(self): + """ Event is not a valid condition. Will it still work? """ + entity_id = 'test.entity' + self.hass.states.set(entity_id, 100) + automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': [ + { + 'platform': 'event', + 'event_type': 'test_event', + }, + { + 'platform': 'numeric_state', + 'entity_id': entity_id, + 'below': 150 + } + ], + 'condition': 'use_trigger_values', + 'action': { + 'execute_service': 'test.automation', + } + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 991c3e066d4..b0410c75014 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -209,6 +209,24 @@ class TestAutomationState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + def test_if_fires_on_entity_change_with_state_filter(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'state', + 'entity_id': 'test.entity', + 'state': 'world' + }, + 'action': { + 'execute_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_entity_change_with_both_filters(self): self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 96579af9aa8..f7187592c66 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -241,7 +241,6 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) - self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -260,7 +259,6 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) - self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -279,7 +277,6 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) - self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -301,7 +298,25 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=0, second=0)) - self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_using_after(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + 'trigger': { + 'platform': 'time', + 'after': '5:00:00', + }, + 'action': { + 'execute_service': 'test.automation' + } + } + })) + + fire_time_changed(self.hass, dt_util.utcnow().replace( + hour=5, minute=0, second=0)) + self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls))