From 68c1dd7cd47c516df765a1742a62abca5c3d5f83 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Sep 2015 22:05:40 -0700 Subject: [PATCH] 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))