diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index d99043f0c75..fe443515e8a 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -4,19 +4,26 @@ Allow to setup simple automation rules via the config file. For more details about this component, please refer to the documentation at https://home-assistant.io/components/automation/ """ +from functools import partial import logging import voluptuous as vol from homeassistant.bootstrap import prepare_setup_platform -from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM +from homeassistant.const import ( + ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, + SERVICE_TOGGLE) from homeassistant.components import logbook from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import extract_domain_configs, script, condition +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_component import EntityComponent from homeassistant.loader import get_platform +from homeassistant.util.dt import utcnow import homeassistant.helpers.config_validation as cv DOMAIN = 'automation' +ENTITY_ID_FORMAT = DOMAIN + '.{}' DEPENDENCIES = ['group'] @@ -36,6 +43,10 @@ DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND METHOD_TRIGGER = 'trigger' METHOD_IF_ACTION = 'if_action' +ATTR_LAST_TRIGGERED = 'last_triggered' +ATTR_VARIABLES = 'variables' +SERVICE_TRIGGER = 'trigger' + _LOGGER = logging.getLogger(__name__) @@ -88,41 +99,170 @@ PLATFORM_SCHEMA = vol.Schema({ vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE): vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)), - CONF_CONDITION: _CONDITION_SCHEMA, + vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, }) +SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, +}) + +TRIGGER_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_VARIABLES, default={}): dict, +}) + + +def is_on(hass, entity_id=None): + """ + Return true if specified automation entity_id is on. + + Check all automation if no entity_id specified. + """ + entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN) + return any(hass.states.is_state(entity_id, STATE_ON) + for entity_id in entity_ids) + + +def turn_on(hass, entity_id=None): + """Turn on specified automation or all.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_TURN_ON, data) + + +def turn_off(hass, entity_id=None): + """Turn off specified automation or all.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_TURN_OFF, data) + + +def toggle(hass, entity_id=None): + """Toggle specified automation or all.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_TOGGLE, data) + + +def trigger(hass, entity_id=None): + """Trigger specified automation or all.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_TRIGGER, data) + def setup(hass, config): """Setup the automation.""" + component = EntityComponent(_LOGGER, DOMAIN, hass) + success = False for config_key in extract_domain_configs(config, DOMAIN): conf = config[config_key] for list_no, config_block in enumerate(conf): - name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key, - list_no)) - success = (_setup_automation(hass, config_block, name, config) or - success) + name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, + list_no) - return success + action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) + if CONF_CONDITION in config_block: + cond_func = _process_if(hass, config, config_block) -def _setup_automation(hass, config_block, name, config): - """Setup one instance of automation.""" - action = _get_action(hass, config_block.get(CONF_ACTION, {}), name) + if cond_func is None: + continue + else: + def cond_func(variables): + """Condition will always pass.""" + return True - if CONF_CONDITION in config_block: - action = _process_if(hass, config, config_block, action) + attach_triggers = partial(_process_trigger, hass, config, + config_block.get(CONF_TRIGGER, []), name) + entity = AutomationEntity(name, attach_triggers, cond_func, action) + component.add_entities((entity,)) + success = True - if action is None: - return False + if not success: + return False + + def trigger_service_handler(service_call): + """Handle automation triggers.""" + for entity in component.extract_from_service(service_call): + entity.trigger(service_call.data.get(ATTR_VARIABLES)) + + def service_handler(service_call): + """Handle automation service calls.""" + for entity in component.extract_from_service(service_call): + getattr(entity, service_call.service)() + + hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler, + schema=TRIGGER_SERVICE_SCHEMA) + + for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE): + hass.services.register(DOMAIN, service, service_handler, + schema=SERVICE_SCHEMA) - _process_trigger(hass, config, config_block.get(CONF_TRIGGER, []), name, - action) return True +class AutomationEntity(ToggleEntity): + """Entity to show status of entity.""" + + def __init__(self, name, attach_triggers, cond_func, action): + """Initialize an automation entity.""" + self._name = name + self._attach_triggers = attach_triggers + self._detach_triggers = attach_triggers(self.trigger) + self._cond_func = cond_func + self._action = action + self._enabled = True + self._last_triggered = None + + @property + def name(self): + """Name of the automation.""" + return self._name + + @property + def should_poll(self): + """No polling needed for automation entities.""" + return False + + @property + def state_attributes(self): + """Return the entity state attributes.""" + return { + ATTR_LAST_TRIGGERED: self._last_triggered + } + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return self._enabled + + def turn_on(self, **kwargs) -> None: + """Turn the entity on.""" + if self._enabled: + return + + self._detach_triggers = self._attach_triggers(self.trigger) + self._enabled = True + self.update_ha_state() + + def turn_off(self, **kwargs) -> None: + """Turn the entity off.""" + if not self._enabled: + return + + self._detach_triggers() + self._detach_triggers = None + self._enabled = False + self.update_ha_state() + + def trigger(self, variables): + """Trigger automation.""" + if self._cond_func(variables): + self._action(variables) + self._last_triggered = utcnow() + self.update_ha_state() + + def _get_action(hass, config, name): """Return an action based on a configuration.""" script_obj = script.Script(hass, config, name) @@ -136,7 +276,7 @@ def _get_action(hass, config, name): return action -def _process_if(hass, config, p_config, action): +def _process_if(hass, config, p_config): """Process if checks.""" cond_type = p_config.get(CONF_CONDITION_TYPE, DEFAULT_CONDITION_TYPE).lower() @@ -182,29 +322,43 @@ def _process_if(hass, config, p_config, action): if cond_type == CONDITION_TYPE_AND: def if_action(variables=None): """AND all conditions.""" - if all(check(hass, variables) for check in checks): - action(variables) + return all(check(hass, variables) for check in checks) else: def if_action(variables=None): """OR all conditions.""" - if any(check(hass, variables) for check in checks): - action(variables) + return any(check(hass, variables) for check in checks) return if_action def _process_trigger(hass, config, trigger_configs, name, action): """Setup the triggers.""" + removes = [] + for conf in trigger_configs: platform = _resolve_platform(METHOD_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: + remove = platform.trigger(hass, conf, action) + + if not remove: _LOGGER.error("Error setting up rule %s", name) + continue + + _LOGGER.info("Initialized rule %s", name) + removes.append(remove) + + if not removes: + return None + + def remove_triggers(): + """Remove attached triggers.""" + for remove in removes: + remove() + + return remove_triggers def _resolve_platform(method, hass, config, platform): diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 6b3160996f3..795dd94a71f 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -39,5 +39,4 @@ def trigger(hass, config, action): }, }) - hass.bus.listen(event_type, handle_event) - return True + return hass.bus.listen(event_type, handle_event) diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index e4a6b221e04..5cd60ff0cea 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -39,6 +39,4 @@ def trigger(hass, config, action): } }) - mqtt.subscribe(hass, topic, mqtt_automation_listener) - - return True + return mqtt.subscribe(hass, topic, mqtt_automation_listener) diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 3a148b0880f..608063b4708 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -63,7 +63,4 @@ def trigger(hass, config, action): action(variables) - track_state_change( - hass, entity_id, state_automation_listener) - - return True + return track_state_change(hass, entity_id, state_automation_listener) diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index d0044bc8c4b..c36466311a9 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -38,9 +38,13 @@ def trigger(hass, config, action): from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL time_delta = config.get(CONF_FOR) + remove_state_for_cancel = None + remove_state_for_listener = None def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" + nonlocal remove_state_for_cancel, remove_state_for_listener + def call_action(): """Call action with right context.""" action({ @@ -75,7 +79,17 @@ def trigger(hass, config, action): remove_state_for_cancel = track_state_change( hass, entity, state_for_cancel_listener) - track_state_change( - hass, entity_id, state_automation_listener, from_state, to_state) + unsub = track_state_change(hass, entity_id, state_automation_listener, + from_state, to_state) - return True + def remove(): + """Remove state listeners.""" + unsub() + + if remove_state_for_cancel is not None: + remove_state_for_cancel() + + if remove_state_for_listener is not None: + remove_state_for_listener() + + return remove diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 7666847575e..991f9b3b385 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -42,8 +42,6 @@ def trigger(hass, config, action): # Do something to call action if event == SUN_EVENT_SUNRISE: - track_sunrise(hass, call_action, offset) + return track_sunrise(hass, call_action, offset) else: - track_sunset(hass, call_action, offset) - - return True + return track_sunset(hass, call_action, offset) diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 1cfbf45a24d..0891590a539 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -49,5 +49,4 @@ def trigger(hass, config, action): elif not template_result: already_triggered = False - track_state_change(hass, MATCH_ALL, state_changed_listener) - return True + return track_state_change(hass, MATCH_ALL, state_changed_listener) diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index ca80536ea96..0732e2b212c 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -47,7 +47,5 @@ def trigger(hass, config, action): }, }) - track_time_change(hass, time_automation_listener, - hour=hours, minute=minutes, second=seconds) - - return True + return track_time_change(hass, time_automation_listener, + hour=hours, minute=minutes, second=seconds) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 5578bf052c4..ec948684805 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -58,7 +58,5 @@ def trigger(hass, config, action): }, }) - track_state_change( - hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL) - - return True + return track_state_change(hass, entity_id, zone_automation_listener, + MATCH_ALL, MATCH_ALL) diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index ef5d380075b..80b1f507651 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -44,6 +44,13 @@ class TestAutomationEvent(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_event_with_data(self): """Test the firing of events with data.""" assert _setup_component(self.hass, automation.DOMAIN, { diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index e90ffe8d765..744bd0becfb 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -1,9 +1,11 @@ """The tests for the automation component.""" import unittest +from unittest.mock import patch from homeassistant.bootstrap import _setup_component import homeassistant.components.automation as automation from homeassistant.const import ATTR_ENTITY_ID +import homeassistant.util.dt as dt_util from tests.common import get_test_home_assistant @@ -45,6 +47,7 @@ class TestAutomation(unittest.TestCase): """Test service data.""" assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { + 'alias': 'hello', 'trigger': { 'platform': 'event', 'event_type': 'test_event', @@ -59,10 +62,17 @@ class TestAutomation(unittest.TestCase): } }) - self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) - self.assertEqual('event - test_event', self.calls[0].data['some']) + time = dt_util.utcnow() + + with patch('homeassistant.components.automation.utcnow', + return_value=time): + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + assert len(self.calls) == 1 + assert 'event - test_event' == self.calls[0].data['some'] + state = self.hass.states.get('automation.hello') + assert state is not None + assert state.attributes.get('last_triggered') == time def test_service_specify_entity_id(self): """Test service data.""" @@ -347,3 +357,60 @@ class TestAutomation(unittest.TestCase): assert len(self.calls) == 2 assert self.calls[0].data['position'] == 0 assert self.calls[1].data['position'] == 1 + + def test_services(self): + """ """ + entity_id = 'automation.hello' + + assert self.hass.states.get(entity_id) is None + assert not automation.is_on(self.hass, entity_id) + + assert _setup_component(self.hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + } + } + }) + + assert self.hass.states.get(entity_id) is not None + assert automation.is_on(self.hass, entity_id) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + assert len(self.calls) == 1 + + automation.turn_off(self.hass, entity_id) + self.hass.pool.block_till_done() + + assert not automation.is_on(self.hass, entity_id) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + assert len(self.calls) == 1 + + automation.toggle(self.hass, entity_id) + self.hass.pool.block_till_done() + + assert automation.is_on(self.hass, entity_id) + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + assert len(self.calls) == 2 + + automation.trigger(self.hass, entity_id) + self.hass.pool.block_till_done() + assert len(self.calls) == 3 + + automation.turn_off(self.hass, entity_id) + self.hass.pool.block_till_done() + automation.trigger(self.hass, entity_id) + self.hass.pool.block_till_done() + assert len(self.calls) == 4 + + automation.turn_on(self.hass, entity_id) + self.hass.pool.block_till_done() + assert automation.is_on(self.hass, entity_id) diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 29d55b424f2..9bd22d0675c 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -50,6 +50,12 @@ class TestAutomationMQTT(unittest.TestCase): self.assertEqual('mqtt - test-topic - test_payload', self.calls[0].data['some']) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + fire_mqtt_message(self.hass, 'test-topic', 'test_payload') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_topic_and_payload_match(self): """Test if message is fired on topic and payload match.""" assert _setup_component(self.hass, automation.DOMAIN, { diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index f7d1447632f..9ee8514052c 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -45,6 +45,14 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + # Set above 12 so the automation will fire again + self.hass.states.set('test.entity', 12) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + self.hass.states.set('test.entity', 9) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_entity_change_over_to_below(self): """"Test the firing with changed entity.""" self.hass.states.set('test.entity', 11) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 4a6971124b6..0b715cb365c 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -59,6 +59,12 @@ class TestAutomationState(unittest.TestCase): 'state - test.entity - hello - world - None', self.calls[0].data['some']) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + self.hass.states.set('test.entity', 'planet') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_entity_change_with_from_filter(self): """Test for firing on entity change with filter.""" assert _setup_component(self.hass, automation.DOMAIN, { diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index 745e7c060ca..d3bbd254e1b 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -54,6 +54,18 @@ class TestAutomationSun(unittest.TestCase): } }) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + + fire_time_changed(self.hass, trigger_time) + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + with patch('homeassistant.util.dt.utcnow', + return_value=now): + automation.turn_on(self.hass) + self.hass.pool.block_till_done() + fire_time_changed(self.hass, trigger_time) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index a643b731492..a33da951cc8 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -45,6 +45,13 @@ class TestAutomationTemplate(unittest.TestCase): self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + + self.hass.states.set('test.entity', 'planet') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + def test_if_fires_on_change_str(self): """Test for firing on change.""" assert _setup_component(self.hass, automation.DOMAIN, { @@ -149,6 +156,9 @@ class TestAutomationTemplate(unittest.TestCase): } }) + self.hass.pool.block_till_done() + self.calls = [] + self.hass.states.set('test.entity', 'hello') self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) @@ -209,9 +219,12 @@ class TestAutomationTemplate(unittest.TestCase): } }) + self.hass.pool.block_till_done() + self.calls = [] + self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() - self.assertEqual(0, len(self.calls)) + assert len(self.calls) == 0 def test_if_fires_on_change_with_template_advanced(self): """Test for firing on change with template advanced.""" @@ -237,6 +250,9 @@ class TestAutomationTemplate(unittest.TestCase): } }) + self.hass.pool.block_till_done() + self.calls = [] + self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) @@ -287,29 +303,32 @@ class TestAutomationTemplate(unittest.TestCase): } }) + self.hass.pool.block_till_done() + self.calls = [] + self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() - self.assertEqual(0, len(self.calls)) + assert len(self.calls) == 0 self.hass.states.set('test.entity', 'home') self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + assert len(self.calls) == 1 self.hass.states.set('test.entity', 'work') self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + assert len(self.calls) == 1 self.hass.states.set('test.entity', 'not_home') self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + assert len(self.calls) == 1 self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() - self.assertEqual(1, len(self.calls)) + assert len(self.calls) == 1 self.hass.states.set('test.entity', 'home') self.hass.pool.block_till_done() - self.assertEqual(2, len(self.calls)) + assert len(self.calls) == 2 def test_if_action(self): """Test for firing if action.""" diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index b36ce8c92b5..3c195f2eb38 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -43,7 +43,13 @@ class TestAutomationTime(unittest.TestCase): }) fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + + fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 24980b466bf..9d4161547ef 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -74,6 +74,24 @@ class TestAutomationZone(unittest.TestCase): 'zone - test.entity - hello - hello - test', self.calls[0].data['some']) + # Set out of zone again so we can trigger call + self.hass.states.set('test.entity', 'hello', { + 'latitude': 32.881011, + 'longitude': -117.234758 + }) + self.hass.pool.block_till_done() + + automation.turn_off(self.hass) + self.hass.pool.block_till_done() + + self.hass.states.set('test.entity', 'hello', { + 'latitude': 32.880586, + 'longitude': -117.237564 + }) + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + def test_if_not_fires_for_enter_on_zone_leave(self): """Test for not firing on zone leave.""" self.hass.states.set('test.entity', 'hello', {