diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 451a0c538b8..66c7c763cc9 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -82,8 +82,7 @@ _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) PLATFORM_SCHEMA = vol.Schema({ CONF_ALIAS: cv.string, - vol.Optional(CONF_INITIAL_STATE, - default=DEFAULT_INITIAL_STATE): cv.boolean, + vol.Optional(CONF_INITIAL_STATE): cv.boolean, vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, @@ -102,15 +101,13 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({ RELOAD_SERVICE_SCHEMA = vol.Schema({}) -def is_on(hass, entity_id=None): +def is_on(hass, entity_id): """ Return true if specified automation entity_id is on. - Check all automation if no entity_id specified. + Async friendly. """ - 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) + return hass.states.is_state(entity_id, STATE_ON) def turn_on(hass, entity_id=None): @@ -232,7 +229,6 @@ class AutomationEntity(ToggleEntity): self._async_detach_triggers = None self._cond_func = cond_func self._async_action = async_action - self._enabled = False self._last_triggered = None self._hidden = hidden self._initial_state = initial_state @@ -262,24 +258,26 @@ class AutomationEntity(ToggleEntity): @property def is_on(self) -> bool: """Return True if entity is on.""" - return self._enabled + return self._async_detach_triggers is not None @asyncio.coroutine def async_added_to_hass(self) -> None: """Startup with initial state or previous state.""" - enable_automation = False + enable_automation = DEFAULT_INITIAL_STATE - state = yield from async_get_last_state(self.hass, self.entity_id) - if state is None: - if self._initial_state: - enable_automation = True + if self._initial_state is not None: + enable_automation = self._initial_state else: - self._last_triggered = state.attributes.get('last_triggered') - if state.state == STATE_ON: - enable_automation = True + state = yield from async_get_last_state(self.hass, self.entity_id) + if state: + enable_automation = state.state == STATE_ON + self._last_triggered = state.attributes.get('last_triggered') - # HomeAssistant is on bootstrap - if enable_automation and self.hass.state == CoreState.not_running: + if not enable_automation: + return + + # HomeAssistant is starting up + elif self.hass.state == CoreState.not_running: @asyncio.coroutine def async_enable_automation(event): """Start automation on startup.""" @@ -289,27 +287,25 @@ class AutomationEntity(ToggleEntity): EVENT_HOMEASSISTANT_START, async_enable_automation) # HomeAssistant is running - elif enable_automation: + else: yield from self.async_enable() @asyncio.coroutine def async_turn_on(self, **kwargs) -> None: """Turn the entity on and update the state.""" - if self._enabled: + if self.is_on: return yield from self.async_enable() - yield from self.async_update_ha_state() @asyncio.coroutine def async_turn_off(self, **kwargs) -> None: """Turn the entity off.""" - if not self._enabled: + if not self.is_on: return self._async_detach_triggers() self._async_detach_triggers = None - self._enabled = False yield from self.async_update_ha_state() @asyncio.coroutine @@ -335,12 +331,12 @@ class AutomationEntity(ToggleEntity): This method is a coroutine. """ - if self._enabled: + if self.is_on: return self._async_detach_triggers = yield from self._async_attach_triggers( self.async_trigger) - self._enabled = True + yield from self.async_update_ha_state() @asyncio.coroutine @@ -359,7 +355,7 @@ def _async_process_config(hass, config, component): list_no) hidden = config_block[CONF_HIDE_ENTITY] - initial_state = config_block[CONF_INITIAL_STATE] + initial_state = config_block.get(CONF_INITIAL_STATE) action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index d60c1b854c3..e003e6311b9 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -277,7 +277,10 @@ def _async_setup_discovery(hass, config): @asyncio.coroutine def async_setup(hass, config): """Start the MQTT protocol service.""" - conf = config.get(DOMAIN, {}) + conf = config.get(DOMAIN) + + if conf is None: + conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN] client_id = conf.get(CONF_CLIENT_ID) keepalive = conf.get(CONF_KEEPALIVE) diff --git a/homeassistant/setup.py b/homeassistant/setup.py index faf412a4e86..f10e3f21124 100644 --- a/homeassistant/setup.py +++ b/homeassistant/setup.py @@ -253,7 +253,7 @@ def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str, if not dep_success: log_error('Could not setup all dependencies.') - return False + return None if not hass.config.skip_pip and hasattr(platform, 'REQUIREMENTS'): req_success = yield from _async_process_requirements( diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 629ee8e2975..71f9fb83b65 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -14,7 +14,7 @@ import homeassistant.util.dt as dt_util from tests.common import ( assert_setup_component, get_test_home_assistant, fire_time_changed, - mock_component, mock_service, mock_restore_cache) + mock_service, mock_restore_cache) # pylint: disable=invalid-name @@ -24,7 +24,6 @@ class TestAutomation(unittest.TestCase): def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - mock_component(self.hass, 'group') self.calls = mock_service(self.hass, 'test', 'automation') def tearDown(self): @@ -156,46 +155,6 @@ class TestAutomation(unittest.TestCase): self.assertEqual(['hello.world'], self.calls[0].data.get(ATTR_ENTITY_ID)) - def test_service_initial_value_off(self): - """Test initial value off.""" - entity_id = 'automation.hello' - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'initial_state': 'off', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'entity_id': ['hello.world', 'hello.world2'] - } - } - }) - assert not automation.is_on(self.hass, entity_id) - - def test_service_initial_value_on(self): - """Test initial value on.""" - entity_id = 'automation.hello' - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'alias': 'hello', - 'initial_state': 'on', - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'entity_id': ['hello.world', 'hello.world2'] - } - } - }) - assert automation.is_on(self.hass, entity_id) - def test_service_specify_entity_id_list(self): """Test service data.""" assert setup_component(self.hass, automation.DOMAIN, { @@ -569,38 +528,6 @@ class TestAutomation(unittest.TestCase): self.hass.block_till_done() assert len(self.calls) == 2 - def test_automation_not_trigger_on_bootstrap(self): - """Test if automation is not trigger on bootstrap.""" - self.hass.state = CoreState.not_running - - assert setup_component(self.hass, automation.DOMAIN, { - automation.DOMAIN: { - 'trigger': { - 'platform': 'event', - 'event_type': 'test_event', - }, - 'action': { - 'service': 'test.automation', - 'entity_id': 'hello.world' - } - } - }) - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert len(self.calls) == 0 - - self.hass.bus.fire(EVENT_HOMEASSISTANT_START) - self.hass.block_till_done() - self.hass.states = CoreState.running - - self.hass.bus.fire('test_event') - self.hass.block_till_done() - - assert len(self.calls) == 1 - assert ['hello.world'] == self.calls[0].data.get(ATTR_ENTITY_ID) - @asyncio.coroutine def test_automation_restore_state(hass): @@ -653,3 +580,209 @@ def test_automation_restore_state(hass): yield from hass.async_block_till_done() assert len(calls) == 1 + + +@asyncio.coroutine +def test_initial_value_off(hass): + """Test initial value off.""" + calls = mock_service(hass, 'test', 'automation') + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'initial_state': 'off', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert not automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 0 + + +@asyncio.coroutine +def test_initial_value_on(hass): + """Test initial value on.""" + calls = mock_service(hass, 'test', 'automation') + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'initial_state': 'on', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': ['hello.world', 'hello.world2'] + } + } + }) + assert res + assert automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 1 + + +@asyncio.coroutine +def test_initial_value_off_but_restore_on(hass): + """Test initial value off and restored state is turned on.""" + calls = mock_service(hass, 'test', 'automation') + mock_restore_cache(hass, ( + State('automation.hello', STATE_ON), + )) + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'initial_state': 'off', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert not automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 0 + + +@asyncio.coroutine +def test_initial_value_on_but_restore_off(hass): + """Test initial value on and restored state is turned off.""" + calls = mock_service(hass, 'test', 'automation') + mock_restore_cache(hass, ( + State('automation.hello', STATE_OFF), + )) + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'initial_state': 'on', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 1 + + +@asyncio.coroutine +def test_no_initial_value_and_restore_off(hass): + """Test initial value off and restored state is turned on.""" + calls = mock_service(hass, 'test', 'automation') + mock_restore_cache(hass, ( + State('automation.hello', STATE_OFF), + )) + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert not automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 0 + + +@asyncio.coroutine +def test_automation_is_on_if_no_initial_state_or_restore(hass): + """Test initial value is on when no initial state or restored state.""" + calls = mock_service(hass, 'test', 'automation') + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 1 + + +@asyncio.coroutine +def test_automation_not_trigger_on_bootstrap(hass): + """Test if automation is not trigger on bootstrap.""" + hass.state = CoreState.not_running + calls = mock_service(hass, 'test', 'automation') + + res = yield from async_setup_component(hass, automation.DOMAIN, { + automation.DOMAIN: { + 'alias': 'hello', + 'trigger': { + 'platform': 'event', + 'event_type': 'test_event', + }, + 'action': { + 'service': 'test.automation', + 'entity_id': 'hello.world' + } + } + }) + assert res + assert not automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + assert len(calls) == 0 + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + yield from hass.async_block_till_done() + assert automation.is_on(hass, 'automation.hello') + + hass.bus.async_fire('test_event') + yield from hass.async_block_till_done() + + assert len(calls) == 1 + assert ['hello.world'] == calls[0].data.get(ATTR_ENTITY_ID)