From fb4de7211bbffc9465987fabc97dc05d1566ac5a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 24 Feb 2022 08:36:36 -0800 Subject: [PATCH] Make Google sync_seralize a callback (#67155) --- .../components/google_assistant/helpers.py | 104 ++++++++---------- .../components/google_assistant/smart_home.py | 24 ++-- .../google_assistant/test_helpers.py | 25 ++--- .../google_assistant/test_smart_home.py | 2 +- 4 files changed, 70 insertions(+), 85 deletions(-) diff --git a/homeassistant/components/google_assistant/helpers.py b/homeassistant/components/google_assistant/helpers.py index a348299e282..b1096d44d74 100644 --- a/homeassistant/components/google_assistant/helpers.py +++ b/homeassistant/components/google_assistant/helpers.py @@ -20,10 +20,7 @@ from homeassistant.const import ( STATE_UNAVAILABLE, ) from homeassistant.core import Context, HomeAssistant, State, callback -from homeassistant.helpers import start -from homeassistant.helpers.area_registry import AreaEntry -from homeassistant.helpers.device_registry import DeviceEntry -from homeassistant.helpers.entity_registry import RegistryEntry +from homeassistant.helpers import area_registry, device_registry, entity_registry, start from homeassistant.helpers.event import async_call_later from homeassistant.helpers.network import get_url from homeassistant.helpers.storage import Store @@ -48,51 +45,33 @@ SYNC_DELAY = 15 _LOGGER = logging.getLogger(__name__) -async def _get_entity_and_device( +@callback +def _get_registry_entries( hass: HomeAssistant, entity_id: str -) -> tuple[RegistryEntry, DeviceEntry] | None: - """Fetch the entity and device entries for a entity_id.""" - dev_reg, ent_reg = await gather( - hass.helpers.device_registry.async_get_registry(), - hass.helpers.entity_registry.async_get_registry(), - ) +) -> tuple[device_registry.DeviceEntry, area_registry.AreaEntry]: + """Get registry entries.""" + ent_reg = entity_registry.async_get(hass) + dev_reg = device_registry.async_get(hass) + area_reg = area_registry.async_get(hass) - if not (entity_entry := ent_reg.async_get(entity_id)): - return None, None - device_entry = dev_reg.devices.get(entity_entry.device_id) - return entity_entry, device_entry + if (entity_entry := ent_reg.async_get(entity_id)) and entity_entry.device_id: + device_entry = dev_reg.devices.get(entity_entry.device_id) + else: + device_entry = None - -async def _get_area( - hass: HomeAssistant, - entity_entry: RegistryEntry | None, - device_entry: DeviceEntry | None, -) -> AreaEntry | None: - """Calculate the area for an entity.""" if entity_entry and entity_entry.area_id: area_id = entity_entry.area_id elif device_entry and device_entry.area_id: area_id = device_entry.area_id else: - return None + area_id = None - area_reg = await hass.helpers.area_registry.async_get_registry() - return area_reg.areas.get(area_id) + if area_id is not None: + area_entry = area_reg.async_get_area(area_id) + else: + area_entry = None - -async def _get_device_info(device_entry: DeviceEntry | None) -> dict[str, str] | None: - """Retrieve the device info for a device.""" - if not device_entry: - return None - - device_info = {} - if device_entry.manufacturer: - device_info["manufacturer"] = device_entry.manufacturer - if device_entry.model: - device_info["model"] = device_entry.model - if device_entry.sw_version: - device_info["swVersion"] = device_entry.sw_version - return device_info + return device_entry, area_entry class AbstractConfig(ABC): @@ -559,60 +538,71 @@ class GoogleEntity: trait.might_2fa(domain, features, device_class) for trait in self.traits() ) - async def sync_serialize(self, agent_user_id): + def sync_serialize(self, agent_user_id, instance_uuid): """Serialize entity for a SYNC response. https://developers.google.com/actions/smarthome/create-app#actiondevicessync """ state = self.state - + traits = self.traits() entity_config = self.config.entity_config.get(state.entity_id, {}) name = (entity_config.get(CONF_NAME) or state.name).strip() - domain = state.domain - device_class = state.attributes.get(ATTR_DEVICE_CLASS) - entity_entry, device_entry = await _get_entity_and_device( - self.hass, state.entity_id - ) - traits = self.traits() - - device_type = get_google_type(domain, device_class) + # Find entity/device/area registry entries + device_entry, area_entry = _get_registry_entries(self.hass, self.entity_id) + # Build the device info device = { "id": state.entity_id, "name": {"name": name}, "attributes": {}, "traits": [trait.name for trait in traits], "willReportState": self.config.should_report_state, - "type": device_type, + "type": get_google_type( + state.domain, state.attributes.get(ATTR_DEVICE_CLASS) + ), } - # use aliases + # Add aliases if aliases := entity_config.get(CONF_ALIASES): device["name"]["nicknames"] = [name] + aliases + # Add local SDK info if enabled if self.config.is_local_sdk_active and self.should_expose_local(): device["otherDeviceIds"] = [{"deviceId": self.entity_id}] device["customData"] = { "webhookId": self.config.get_local_webhook_id(agent_user_id), "httpPort": self.hass.http.server_port, "httpSSL": self.hass.config.api.use_ssl, - "uuid": await self.hass.helpers.instance_id.async_get(), + "uuid": instance_uuid, "baseUrl": get_url(self.hass, prefer_external=True), "proxyDeviceId": agent_user_id, } + # Add trait sync attributes for trt in traits: device["attributes"].update(trt.sync_attributes()) + # Add roomhint if room := entity_config.get(CONF_ROOM_HINT): device["roomHint"] = room - else: - area = await _get_area(self.hass, entity_entry, device_entry) - if area and area.name: - device["roomHint"] = area.name + elif area_entry and area_entry.name: + device["roomHint"] = area_entry.name - if device_info := await _get_device_info(device_entry): + # Add deviceInfo + if not device_entry: + return device + + device_info = {} + + if device_entry.manufacturer: + device_info["manufacturer"] = device_entry.manufacturer + if device_entry.model: + device_info["model"] = device_entry.model + if device_entry.sw_version: + device_info["swVersion"] = device_entry.sw_version + + if device_info: device["deviceInfo"] = device_info return device diff --git a/homeassistant/components/google_assistant/smart_home.py b/homeassistant/components/google_assistant/smart_home.py index 5f38194e3e3..f2aea247ecd 100644 --- a/homeassistant/components/google_assistant/smart_home.py +++ b/homeassistant/components/google_assistant/smart_home.py @@ -4,6 +4,7 @@ from itertools import product import logging from homeassistant.const import ATTR_ENTITY_ID, __version__ +from homeassistant.helpers import instance_id from homeassistant.util.decorator import Registry from .const import ( @@ -86,22 +87,17 @@ async def async_devices_sync(hass, data, payload): await data.config.async_connect_agent_user(agent_user_id) entities = async_get_entities(hass, data.config) - results = await asyncio.gather( - *( - entity.sync_serialize(agent_user_id) - for entity in entities - if entity.should_expose() - ), - return_exceptions=True, - ) - + instance_uuid = await instance_id.async_get(hass) devices = [] - for entity, result in zip(entities, results): - if isinstance(result, Exception): - _LOGGER.error("Error serializing %s", entity.entity_id, exc_info=result) - else: - devices.append(result) + for entity in entities: + if not entity.should_expose(): + continue + + try: + devices.append(entity.sync_serialize(agent_user_id, instance_uuid)) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Error serializing %s", entity.entity_id) response = {"agentUserId": agent_user_id, "devices": devices} diff --git a/tests/components/google_assistant/test_helpers.py b/tests/components/google_assistant/test_helpers.py index dc29e5df4ab..4490f0e3963 100644 --- a/tests/components/google_assistant/test_helpers.py +++ b/tests/components/google_assistant/test_helpers.py @@ -47,30 +47,29 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass): ) entity = helpers.GoogleEntity(hass, config, hass.states.get("light.ceiling_lights")) - serialized = await entity.sync_serialize(None) + serialized = entity.sync_serialize(None, "mock-uuid") assert "otherDeviceIds" not in serialized assert "customData" not in serialized config.async_enable_local_sdk() - with patch("homeassistant.helpers.instance_id.async_get", return_value="abcdef"): - serialized = await entity.sync_serialize("mock-user-id") - assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}] - assert serialized["customData"] == { - "httpPort": 1234, - "httpSSL": False, - "proxyDeviceId": "mock-user-id", - "webhookId": "mock-webhook-id", - "baseUrl": "https://hostname:1234", - "uuid": "abcdef", - } + serialized = entity.sync_serialize("mock-user-id", "abcdef") + assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}] + assert serialized["customData"] == { + "httpPort": 1234, + "httpSSL": False, + "proxyDeviceId": "mock-user-id", + "webhookId": "mock-webhook-id", + "baseUrl": "https://hostname:1234", + "uuid": "abcdef", + } for device_type in NOT_EXPOSE_LOCAL: with patch( "homeassistant.components.google_assistant.helpers.get_google_type", return_value=device_type, ): - serialized = await entity.sync_serialize(None) + serialized = entity.sync_serialize(None, "mock-uuid") assert "otherDeviceIds" not in serialized assert "customData" not in serialized diff --git a/tests/components/google_assistant/test_smart_home.py b/tests/components/google_assistant/test_smart_home.py index 3398fdca926..c3bbd9336f4 100644 --- a/tests/components/google_assistant/test_smart_home.py +++ b/tests/components/google_assistant/test_smart_home.py @@ -875,7 +875,7 @@ async def test_serialize_input_boolean(hass): state = State("input_boolean.bla", "on") # pylint: disable=protected-access entity = sh.GoogleEntity(hass, BASIC_CONFIG, state) - result = await entity.sync_serialize(None) + result = entity.sync_serialize(None, "mock-uuid") assert result == { "id": "input_boolean.bla", "attributes": {},