From 7b69e20db7025c08401049db463b1aca2f749b5a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 27 Apr 2022 22:32:13 -0700 Subject: [PATCH] Sync area changes to google (#70936) Co-authored-by: Martin Hjelmare --- .../components/cloud/google_config.py | 42 +++++++++++-- tests/components/cloud/test_google_config.py | 62 ++++++++++++++++++- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/cloud/google_config.py b/homeassistant/components/cloud/google_config.py index e2b21ffc56d..8f190103e87 100644 --- a/homeassistant/components/cloud/google_config.py +++ b/homeassistant/components/cloud/google_config.py @@ -9,8 +9,8 @@ from hass_nabucasa.google_report_state import ErrorResponse from homeassistant.components.google_assistant.const import DOMAIN as GOOGLE_DOMAIN from homeassistant.components.google_assistant.helpers import AbstractConfig from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES -from homeassistant.core import CoreState, split_entity_id -from homeassistant.helpers import entity_registry as er, start +from homeassistant.core import CoreState, Event, callback, split_entity_id +from homeassistant.helpers import device_registry as dr, entity_registry as er, start from homeassistant.setup import async_setup_component from .const import ( @@ -103,6 +103,10 @@ class CloudGoogleConfig(AbstractConfig): er.EVENT_ENTITY_REGISTRY_UPDATED, self._handle_entity_registry_updated, ) + self.hass.bus.async_listen( + dr.EVENT_DEVICE_REGISTRY_UPDATED, + self._handle_device_registry_updated, + ) def should_expose(self, state): """If a state object should be exposed.""" @@ -217,9 +221,14 @@ class CloudGoogleConfig(AbstractConfig): self._cur_entity_prefs = prefs.google_entity_configs self._cur_default_expose = prefs.google_default_expose - async def _handle_entity_registry_updated(self, event): + @callback + def _handle_entity_registry_updated(self, event: Event) -> None: """Handle when entity registry updated.""" - if not self.enabled or not self._cloud.is_logged_in: + if ( + not self.enabled + or not self._cloud.is_logged_in + or self.hass.state != CoreState.running + ): return # Only consider entity registry updates if info relevant for Google has changed @@ -233,7 +242,30 @@ class CloudGoogleConfig(AbstractConfig): if not self._should_expose_entity_id(entity_id): return - if self.hass.state != CoreState.running: + self.async_schedule_google_sync_all() + + @callback + def _handle_device_registry_updated(self, event: Event) -> None: + """Handle when device registry updated.""" + if ( + not self.enabled + or not self._cloud.is_logged_in + or self.hass.state != CoreState.running + ): + return + + # Device registry is only used for area changes. All other changes are ignored. + if event.data["action"] != "update" or "area_id" not in event.data["changes"]: + return + + # Check if any exposed entity uses the device area + if not any( + entity_entry.area_id is None + and self._should_expose_entity_id(entity_entry.entity_id) + for entity_entry in er.async_entries_for_device( + er.async_get(self.hass), event.data["device_id"] + ) + ): return self.async_schedule_google_sync_all() diff --git a/tests/components/cloud/test_google_config.py b/tests/components/cloud/test_google_config.py index 98674ff6c86..95746eb67ae 100644 --- a/tests/components/cloud/test_google_config.py +++ b/tests/components/cloud/test_google_config.py @@ -10,7 +10,7 @@ from homeassistant.components.cloud.google_config import CloudGoogleConfig from homeassistant.components.google_assistant import helpers as ga_helpers from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.core import CoreState, State -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.entity import EntityCategory from homeassistant.util.dt import utcnow @@ -191,6 +191,66 @@ async def test_google_entity_registry_sync(hass, mock_cloud_login, cloud_prefs): assert len(mock_sync.mock_calls) == 3 +async def test_google_device_registry_sync(hass, mock_cloud_login, cloud_prefs): + """Test Google config responds to device registry.""" + config = CloudGoogleConfig( + hass, GACTIONS_SCHEMA({}), "mock-user-id", cloud_prefs, hass.data["cloud"] + ) + ent_reg = er.async_get(hass) + entity_entry = ent_reg.async_get_or_create( + "light", "hue", "1234", device_id="1234", area_id="ABCD" + ) + + with patch.object(config, "async_sync_entities_all"): + await config.async_initialize() + await hass.async_block_till_done() + await config.async_connect_agent_user("mock-user-id") + + with patch.object(config, "async_schedule_google_sync_all") as mock_sync: + # Device registry updated with non-relevant changes + hass.bus.async_fire( + dr.EVENT_DEVICE_REGISTRY_UPDATED, + { + "action": "update", + "device_id": "1234", + "changes": ["manufacturer"], + }, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 0 + + # Device registry updated with relevant changes + # but entity has area ID so not impacted + hass.bus.async_fire( + dr.EVENT_DEVICE_REGISTRY_UPDATED, + { + "action": "update", + "device_id": "1234", + "changes": ["area_id"], + }, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 0 + + ent_reg.async_update_entity(entity_entry.entity_id, area_id=None) + + # Device registry updated with relevant changes + # but entity has area ID so not impacted + hass.bus.async_fire( + dr.EVENT_DEVICE_REGISTRY_UPDATED, + { + "action": "update", + "device_id": "1234", + "changes": ["area_id"], + }, + ) + await hass.async_block_till_done() + + assert len(mock_sync.mock_calls) == 1 + + async def test_sync_google_when_started(hass, mock_cloud_login, cloud_prefs): """Test Google config syncs on init.""" config = CloudGoogleConfig(