From 214d6f5cf541aed9ed77263d9b6ff7f1c9397deb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 24 Apr 2020 14:13:39 -0700 Subject: [PATCH] Add Home Assistant Started event (#34657) --- .../components/automation/__init__.py | 16 +++++---- .../components/automation/homeassistant.py | 4 +-- .../components/cloud/google_config.py | 27 ++++++++++++-- homeassistant/const.py | 1 + homeassistant/core.py | 2 ++ .../automation/test_homeassistant.py | 2 ++ tests/components/automation/test_init.py | 5 +-- tests/components/cloud/test_google_config.py | 36 +++++++++++++------ 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 76fe619cc1c..ea6c1e81e66 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -14,7 +14,7 @@ from homeassistant.const import ( CONF_PLATFORM, CONF_ZONE, EVENT_AUTOMATION_TRIGGERED, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF, @@ -408,7 +408,7 @@ class AutomationEntity(ToggleEntity, RestoreEntity): # HomeAssistant is starting up if self.hass.state != CoreState.not_running: - self._async_detach_triggers = await self._async_attach_triggers() + self._async_detach_triggers = await self._async_attach_triggers(False) self.async_write_ha_state() return @@ -418,10 +418,10 @@ class AutomationEntity(ToggleEntity, RestoreEntity): if not self._is_enabled or self._async_detach_triggers is not None: return - self._async_detach_triggers = await self._async_attach_triggers() + self._async_detach_triggers = await self._async_attach_triggers(True) self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, async_enable_automation + EVENT_HOMEASSISTANT_STARTED, async_enable_automation ) self.async_write_ha_state() @@ -438,15 +438,17 @@ class AutomationEntity(ToggleEntity, RestoreEntity): self.async_write_ha_state() - async def _async_attach_triggers(self): + async def _async_attach_triggers( + self, home_assistant_start: bool + ) -> Optional[Callable[[], None]]: """Set up the triggers.""" removes = [] - info = {"name": self._name} + info = {"name": self._name, "home_assistant_start": home_assistant_start} for conf in self._trigger_config: platform = importlib.import_module(f".{conf[CONF_PLATFORM]}", __name__) - remove = await platform.async_attach_trigger( + remove = await platform.async_attach_trigger( # type: ignore self.hass, conf, self.async_trigger, info ) diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py index 743b169c86c..91b67e28c7c 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -4,7 +4,7 @@ import logging import voluptuous as vol from homeassistant.const import CONF_EVENT, CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP -from homeassistant.core import CoreState, callback +from homeassistant.core import callback # mypy: allow-untyped-defs @@ -40,7 +40,7 @@ async def async_attach_trigger(hass, config, action, automation_info): # Automation are enabled while hass is starting up, fire right away # Check state because a config reload shouldn't trigger it. - if hass.state == CoreState.starting: + if automation_info["home_assistant_start"]: hass.async_run_job( action({"trigger": {"platform": "homeassistant", "event": event}}) ) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index bb6dcaa2fe2..9b94b77ca45 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -6,7 +6,12 @@ from hass_nabucasa import cloud_api from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.helpers import AbstractConfig -from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_OK +from homeassistant.const import ( + CLOUD_NEVER_EXPOSED_ENTITIES, + EVENT_HOMEASSISTANT_STARTED, + HTTP_OK, +) +from homeassistant.core import CoreState, callback from homeassistant.helpers import entity_registry from .const import ( @@ -32,6 +37,7 @@ class CloudGoogleConfig(AbstractConfig): self._cloud = cloud self._cur_entity_prefs = self._prefs.google_entity_configs self._sync_entities_lock = asyncio.Lock() + self._sync_on_started = False @property def enabled(self): @@ -169,6 +175,21 @@ class CloudGoogleConfig(AbstractConfig): entity_id = event.data["entity_id"] - # Schedule a sync if a change was made to an entity that Google knows about - if self._should_expose_entity_id(entity_id): + if not self._should_expose_entity_id(entity_id): + return + + if self.hass.state == CoreState.running: self.async_schedule_google_sync_all() + return + + if self._sync_on_started: + return + + self._sync_on_started = True + + @callback + async def sync_google(_): + """Sync entities to Google.""" + await self.async_sync_entities_all() + + self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, sync_google) diff --git a/homeassistant/const.py b/homeassistant/const.py index 67feb6c7b13..1366103113c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -183,6 +183,7 @@ EVENT_COMPONENT_LOADED = "component_loaded" EVENT_CORE_CONFIG_UPDATE = "core_config_updated" EVENT_HOMEASSISTANT_CLOSE = "homeassistant_close" EVENT_HOMEASSISTANT_START = "homeassistant_start" +EVENT_HOMEASSISTANT_STARTED = "homeassistant_started" EVENT_HOMEASSISTANT_STOP = "homeassistant_stop" EVENT_HOMEASSISTANT_FINAL_WRITE = "homeassistant_final_write" EVENT_LOGBOOK_ENTRY = "logbook_entry" diff --git a/homeassistant/core.py b/homeassistant/core.py index c8c5fc4d499..c799656df89 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -50,6 +50,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_FINAL_WRITE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, @@ -279,6 +280,7 @@ class HomeAssistant: self.state = CoreState.running _async_create_timer(self) + self.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) def add_job(self, target: Callable[..., Any], *args: Any) -> None: """Add job to the executor pool. diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py index ee4293effe3..a0985e54976 100644 --- a/tests/components/automation/test_homeassistant.py +++ b/tests/components/automation/test_homeassistant.py @@ -25,6 +25,7 @@ async def test_if_fires_on_hass_start(hass): assert len(calls) == 0 await hass.async_start() + await hass.async_block_till_done() assert automation.is_on(hass, "automation.hello") assert len(calls) == 1 @@ -61,6 +62,7 @@ async def test_if_fires_on_hass_shutdown(hass): await hass.async_start() assert automation.is_on(hass, "automation.hello") + await hass.async_block_till_done() assert len(calls) == 0 with patch.object(hass.loop, "stop"): diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index c27a0262a4e..cd4a01e9a28 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -10,7 +10,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_NAME, EVENT_AUTOMATION_TRIGGERED, - EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STARTED, STATE_OFF, STATE_ON, ) @@ -700,6 +700,7 @@ async def test_initial_value_on(hass): assert automation.is_on(hass, "automation.hello") await hass.async_start() + await hass.async_block_till_done() hass.bus.async_fire("test_event") await hass.async_block_till_done() assert len(calls) == 1 @@ -822,7 +823,7 @@ async def test_automation_not_trigger_on_bootstrap(hass): await hass.async_block_till_done() assert len(calls) == 0 - hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) await hass.async_block_till_done() assert automation.is_on(hass, "automation.hello") diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 2474851cce8..b08b950a590 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -6,7 +6,8 @@ from asynctest import patch from homeassistant.components.cloud import GACTIONS_SCHEMA from homeassistant.components.cloud.google_config import CloudGoogleConfig from homeassistant.components.google_assistant import helpers as ga_helpers -from homeassistant.const import HTTP_NOT_FOUND +from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, HTTP_NOT_FOUND +from homeassistant.core import CoreState from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.util.dt import utcnow @@ -25,9 +26,7 @@ async def test_google_update_report_state(hass, cloud_prefs): await config.async_initialize() await config.async_connect_agent_user("mock-user-id") - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch( + with patch.object(config, "async_sync_entities") as mock_sync, patch( "homeassistant.components.google_assistant.report_state.async_enable_report_state" ) as mock_report_state: await cloud_prefs.async_update(google_report_state=True) @@ -67,9 +66,9 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs): await config.async_initialize() await config.async_connect_agent_user("mock-user-id") - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + with patch.object(config, "async_sync_entities") as mock_sync, patch.object( + ga_helpers, "SYNC_DELAY", 0 + ): await cloud_prefs.async_update_google_entity_config( entity_id="light.kitchen", should_expose=True ) @@ -79,9 +78,9 @@ async def test_google_update_expose_trigger_sync(hass, cloud_prefs): assert len(mock_sync.mock_calls) == 1 - with patch.object( - config, "async_sync_entities", side_effect=mock_coro - ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): + with patch.object(config, "async_sync_entities") as mock_sync, patch.object( + ga_helpers, "SYNC_DELAY", 0 + ): await cloud_prefs.async_update_google_entity_config( entity_id="light.kitchen", should_expose=False ) @@ -107,7 +106,7 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): await config.async_connect_agent_user("mock-user-id") with patch.object( - config, "async_schedule_google_sync_all", side_effect=mock_coro + config, "async_schedule_google_sync_all" ) as mock_sync, patch.object(ga_helpers, "SYNC_DELAY", 0): # Created entity hass.bus.async_fire( @@ -148,3 +147,18 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): await hass.async_block_till_done() assert len(mock_sync.mock_calls) == 3 + + # When hass is not started yet we wait till started + hass.state = CoreState.starting + hass.bus.async_fire( + EVENT_ENTITY_REGISTRY_UPDATED, + {"action": "create", "entity_id": "light.kitchen"}, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 3 + + with patch.object(config, "async_sync_entities_all") as mock_sync: + hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) + await hass.async_block_till_done() + assert len(mock_sync.mock_calls) == 1