Make Google sync_seralize a callback (#67155)
parent
7068c46f8f
commit
fb4de7211b
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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": {},
|
||||
|
|
Loading…
Reference in New Issue