Make Google sync_seralize a callback (#67155)
This commit is contained in:
parent
7068c46f8f
commit
fb4de7211b
4 changed files with 70 additions and 85 deletions
|
@ -20,10 +20,7 @@ from homeassistant.const import (
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Context, HomeAssistant, State, callback
|
from homeassistant.core import Context, HomeAssistant, State, callback
|
||||||
from homeassistant.helpers import start
|
from homeassistant.helpers import area_registry, device_registry, entity_registry, 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.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.network import get_url
|
from homeassistant.helpers.network import get_url
|
||||||
from homeassistant.helpers.storage import Store
|
from homeassistant.helpers.storage import Store
|
||||||
|
@ -48,51 +45,33 @@ SYNC_DELAY = 15
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def _get_entity_and_device(
|
@callback
|
||||||
|
def _get_registry_entries(
|
||||||
hass: HomeAssistant, entity_id: str
|
hass: HomeAssistant, entity_id: str
|
||||||
) -> tuple[RegistryEntry, DeviceEntry] | None:
|
) -> tuple[device_registry.DeviceEntry, area_registry.AreaEntry]:
|
||||||
"""Fetch the entity and device entries for a entity_id."""
|
"""Get registry entries."""
|
||||||
dev_reg, ent_reg = await gather(
|
ent_reg = entity_registry.async_get(hass)
|
||||||
hass.helpers.device_registry.async_get_registry(),
|
dev_reg = device_registry.async_get(hass)
|
||||||
hass.helpers.entity_registry.async_get_registry(),
|
area_reg = area_registry.async_get(hass)
|
||||||
)
|
|
||||||
|
|
||||||
if not (entity_entry := ent_reg.async_get(entity_id)):
|
if (entity_entry := ent_reg.async_get(entity_id)) and entity_entry.device_id:
|
||||||
return None, None
|
|
||||||
device_entry = dev_reg.devices.get(entity_entry.device_id)
|
device_entry = dev_reg.devices.get(entity_entry.device_id)
|
||||||
return entity_entry, device_entry
|
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:
|
if entity_entry and entity_entry.area_id:
|
||||||
area_id = entity_entry.area_id
|
area_id = entity_entry.area_id
|
||||||
elif device_entry and device_entry.area_id:
|
elif device_entry and device_entry.area_id:
|
||||||
area_id = device_entry.area_id
|
area_id = device_entry.area_id
|
||||||
else:
|
else:
|
||||||
return None
|
area_id = None
|
||||||
|
|
||||||
area_reg = await hass.helpers.area_registry.async_get_registry()
|
if area_id is not None:
|
||||||
return area_reg.areas.get(area_id)
|
area_entry = area_reg.async_get_area(area_id)
|
||||||
|
else:
|
||||||
|
area_entry = None
|
||||||
|
|
||||||
|
return device_entry, area_entry
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractConfig(ABC):
|
class AbstractConfig(ABC):
|
||||||
|
@ -559,60 +538,71 @@ class GoogleEntity:
|
||||||
trait.might_2fa(domain, features, device_class) for trait in self.traits()
|
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.
|
"""Serialize entity for a SYNC response.
|
||||||
|
|
||||||
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
https://developers.google.com/actions/smarthome/create-app#actiondevicessync
|
||||||
"""
|
"""
|
||||||
state = self.state
|
state = self.state
|
||||||
|
traits = self.traits()
|
||||||
entity_config = self.config.entity_config.get(state.entity_id, {})
|
entity_config = self.config.entity_config.get(state.entity_id, {})
|
||||||
name = (entity_config.get(CONF_NAME) or state.name).strip()
|
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()
|
# Find entity/device/area registry entries
|
||||||
|
device_entry, area_entry = _get_registry_entries(self.hass, self.entity_id)
|
||||||
device_type = get_google_type(domain, device_class)
|
|
||||||
|
|
||||||
|
# Build the device info
|
||||||
device = {
|
device = {
|
||||||
"id": state.entity_id,
|
"id": state.entity_id,
|
||||||
"name": {"name": name},
|
"name": {"name": name},
|
||||||
"attributes": {},
|
"attributes": {},
|
||||||
"traits": [trait.name for trait in traits],
|
"traits": [trait.name for trait in traits],
|
||||||
"willReportState": self.config.should_report_state,
|
"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):
|
if aliases := entity_config.get(CONF_ALIASES):
|
||||||
device["name"]["nicknames"] = [name] + aliases
|
device["name"]["nicknames"] = [name] + aliases
|
||||||
|
|
||||||
|
# Add local SDK info if enabled
|
||||||
if self.config.is_local_sdk_active and self.should_expose_local():
|
if self.config.is_local_sdk_active and self.should_expose_local():
|
||||||
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
|
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
|
||||||
device["customData"] = {
|
device["customData"] = {
|
||||||
"webhookId": self.config.get_local_webhook_id(agent_user_id),
|
"webhookId": self.config.get_local_webhook_id(agent_user_id),
|
||||||
"httpPort": self.hass.http.server_port,
|
"httpPort": self.hass.http.server_port,
|
||||||
"httpSSL": self.hass.config.api.use_ssl,
|
"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),
|
"baseUrl": get_url(self.hass, prefer_external=True),
|
||||||
"proxyDeviceId": agent_user_id,
|
"proxyDeviceId": agent_user_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add trait sync attributes
|
||||||
for trt in traits:
|
for trt in traits:
|
||||||
device["attributes"].update(trt.sync_attributes())
|
device["attributes"].update(trt.sync_attributes())
|
||||||
|
|
||||||
|
# Add roomhint
|
||||||
if room := entity_config.get(CONF_ROOM_HINT):
|
if room := entity_config.get(CONF_ROOM_HINT):
|
||||||
device["roomHint"] = room
|
device["roomHint"] = room
|
||||||
else:
|
elif area_entry and area_entry.name:
|
||||||
area = await _get_area(self.hass, entity_entry, device_entry)
|
device["roomHint"] = area_entry.name
|
||||||
if area and area.name:
|
|
||||||
device["roomHint"] = area.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
|
device["deviceInfo"] = device_info
|
||||||
|
|
||||||
return device
|
return device
|
||||||
|
|
|
@ -4,6 +4,7 @@ from itertools import product
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, __version__
|
from homeassistant.const import ATTR_ENTITY_ID, __version__
|
||||||
|
from homeassistant.helpers import instance_id
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
|
|
||||||
from .const import (
|
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)
|
await data.config.async_connect_agent_user(agent_user_id)
|
||||||
|
|
||||||
entities = async_get_entities(hass, data.config)
|
entities = async_get_entities(hass, data.config)
|
||||||
results = await asyncio.gather(
|
instance_uuid = await instance_id.async_get(hass)
|
||||||
*(
|
|
||||||
entity.sync_serialize(agent_user_id)
|
|
||||||
for entity in entities
|
|
||||||
if entity.should_expose()
|
|
||||||
),
|
|
||||||
return_exceptions=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
devices = []
|
devices = []
|
||||||
|
|
||||||
for entity, result in zip(entities, results):
|
for entity in entities:
|
||||||
if isinstance(result, Exception):
|
if not entity.should_expose():
|
||||||
_LOGGER.error("Error serializing %s", entity.entity_id, exc_info=result)
|
continue
|
||||||
else:
|
|
||||||
devices.append(result)
|
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}
|
response = {"agentUserId": agent_user_id, "devices": devices}
|
||||||
|
|
||||||
|
|
|
@ -47,14 +47,13 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass):
|
||||||
)
|
)
|
||||||
entity = helpers.GoogleEntity(hass, config, hass.states.get("light.ceiling_lights"))
|
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 "otherDeviceIds" not in serialized
|
||||||
assert "customData" not in serialized
|
assert "customData" not in serialized
|
||||||
|
|
||||||
config.async_enable_local_sdk()
|
config.async_enable_local_sdk()
|
||||||
|
|
||||||
with patch("homeassistant.helpers.instance_id.async_get", return_value="abcdef"):
|
serialized = entity.sync_serialize("mock-user-id", "abcdef")
|
||||||
serialized = await entity.sync_serialize("mock-user-id")
|
|
||||||
assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}]
|
assert serialized["otherDeviceIds"] == [{"deviceId": "light.ceiling_lights"}]
|
||||||
assert serialized["customData"] == {
|
assert serialized["customData"] == {
|
||||||
"httpPort": 1234,
|
"httpPort": 1234,
|
||||||
|
@ -70,7 +69,7 @@ async def test_google_entity_sync_serialize_with_local_sdk(hass):
|
||||||
"homeassistant.components.google_assistant.helpers.get_google_type",
|
"homeassistant.components.google_assistant.helpers.get_google_type",
|
||||||
return_value=device_type,
|
return_value=device_type,
|
||||||
):
|
):
|
||||||
serialized = await entity.sync_serialize(None)
|
serialized = entity.sync_serialize(None, "mock-uuid")
|
||||||
assert "otherDeviceIds" not in serialized
|
assert "otherDeviceIds" not in serialized
|
||||||
assert "customData" 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")
|
state = State("input_boolean.bla", "on")
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
|
entity = sh.GoogleEntity(hass, BASIC_CONFIG, state)
|
||||||
result = await entity.sync_serialize(None)
|
result = entity.sync_serialize(None, "mock-uuid")
|
||||||
assert result == {
|
assert result == {
|
||||||
"id": "input_boolean.bla",
|
"id": "input_boolean.bla",
|
||||||
"attributes": {},
|
"attributes": {},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue