Make Google sync_seralize a callback (#67155)

pull/67185/head
Paulus Schoutsen 2022-02-24 08:36:36 -08:00 committed by GitHub
parent 7068c46f8f
commit fb4de7211b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 85 deletions

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -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": {},