1 """Support for Google Assistant Smart Home API."""
4 from collections.abc
import Callable, Coroutine
5 from itertools
import product
19 EVENT_COMMAND_RECEIVED,
23 from .data_redaction
import async_redact_msg
24 from .error
import SmartHomeError
25 from .helpers
import GoogleEntity, RequestData, async_get_entities
32 [HomeAssistant, RequestData, dict[str, Any]],
33 Coroutine[Any, Any, dict[str, Any] |
None],
36 _LOGGER = logging.getLogger(__name__)
40 hass, config, agent_user_id, local_user_id, message, source
42 """Handle incoming API messages."""
43 if _LOGGER.isEnabledFor(logging.DEBUG):
45 "Processing message:\n%s",
50 config, local_user_id, source, message[
"requestId"], message.get(
"devices")
53 response = await
_process(hass, data, message)
54 if _LOGGER.isEnabledFor(logging.DEBUG):
61 _LOGGER.debug(
"Empty response")
63 if response
and "errorCode" in response[
"payload"]:
65 "Error handling message\n:%s\nResponse:\n%s",
74 """Process a message."""
75 inputs: list = message.get(
"inputs")
79 "requestId": data.request_id,
80 "payload": {
"errorCode": ERR_PROTOCOL_ERROR},
83 if (handler := HANDLERS.get(inputs[0].
get(
"intent")))
is None:
85 "requestId": data.request_id,
86 "payload": {
"errorCode": ERR_PROTOCOL_ERROR},
90 result = await handler(hass, data, inputs[0].
get(
"payload"))
91 except SmartHomeError
as err:
92 return {
"requestId": data.request_id,
"payload": {
"errorCode": err.code}}
94 _LOGGER.exception(
"Unexpected error")
96 "requestId": data.request_id,
97 "payload": {
"errorCode": ERR_UNKNOWN_ERROR},
103 return {
"requestId": data.request_id,
"payload": result}
107 """Generate the device serialization."""
109 instance_uuid = await instance_id.async_get(hass)
112 for entity
in entities:
113 if not entity.should_expose():
117 devices.append(entity.sync_serialize(agent_user_id, instance_uuid))
119 _LOGGER.exception(
"Error serializing %s", entity.entity_id)
124 @HANDLERS.register("action.devices.SYNC")
126 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
128 """Handle action.devices.SYNC request.
130 https://developers.google.com/assistant/smarthome/develop/process-intents#SYNC
134 {
"request_id": data.request_id,
"source": data.source},
135 context=data.context,
138 agent_user_id = data.config.get_agent_user_id_from_context(data.context)
139 await data.config.async_connect_agent_user(agent_user_id)
145 @HANDLERS.register("action.devices.QUERY")
147 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
149 """Handle action.devices.QUERY request.
151 https://developers.google.com/assistant/smarthome/develop/process-intents#QUERY
153 payload_devices = payload.get(
"devices", [])
156 EVENT_QUERY_RECEIVED,
158 "request_id": data.request_id,
159 ATTR_ENTITY_ID: [device[
"id"]
for device
in payload_devices],
160 "source": data.source,
162 context=data.context,
169 """Generate the device serialization."""
171 for device
in payload_devices:
174 if not (state := hass.states.get(devid)):
176 devices[devid] = {
"online":
False}
181 devices[devid] = entity.query_serialize()
183 _LOGGER.exception(
"Unexpected error serializing query for %s", state)
184 devices[devid] = {
"online":
False}
186 return {
"devices": devices}
190 """Execute all commands for an entity.
192 Returns a dict if a special result needs to be set.
194 for execution
in executions:
196 await entity.execute(data, execution)
197 except SmartHomeError
as err:
199 "ids": [entity.entity_id],
207 @HANDLERS.register("action.devices.EXECUTE")
209 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
211 """Handle action.devices.EXECUTE request.
213 https://developers.google.com/assistant/smarthome/develop/process-intents#EXECUTE
215 entities: dict[str, GoogleEntity] = {}
216 executions: dict[str, list[Any]] = {}
217 results: dict[str, dict[str, Any]] = {}
219 for command
in payload[
"commands"]:
221 EVENT_COMMAND_RECEIVED,
223 "request_id": data.request_id,
224 ATTR_ENTITY_ID: [device[
"id"]
for device
in command[
"devices"]],
225 "execution": command[
"execution"],
226 "source": data.source,
228 context=data.context,
231 for device, execution
in product(command[
"devices"], command[
"execution"]):
232 entity_id = device[
"id"]
235 if entity_id
in results:
238 if entity_id
in entities:
239 executions[entity_id].append(execution)
242 if (state := hass.states.get(entity_id))
is None:
243 results[entity_id] = {
246 "errorCode": ERR_DEVICE_OFFLINE,
250 entities[entity_id] =
GoogleEntity(hass, data.config, state)
251 executions[entity_id] = [execution]
254 execute_results = await asyncio.wait_for(
259 for entity_id, execution
in executions.items()
268 for entity_id, result
in zip(executions, execute_results, strict=
False)
269 if result
is not None
275 final_results =
list(results.values())
277 for entity
in entities.values():
278 if entity.entity_id
in results:
281 entity.async_update()
283 final_results.append(
285 "ids": [entity.entity_id],
287 "states": entity.query_serialize(),
291 return {
"commands": final_results}
294 @HANDLERS.register("action.devices.DISCONNECT")
296 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
298 """Handle action.devices.DISCONNECT request.
300 https://developers.google.com/assistant/smarthome/develop/process-intents#DISCONNECT
302 assert data.context.user_id
is not None
303 await data.config.async_disconnect_agent_user(data.context.user_id)
306 @HANDLERS.register("action.devices.IDENTIFY")
308 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
310 """Handle action.devices.IDENTIFY request.
312 https://developers.google.com/assistant/smarthome/develop/local#implement_the_identify_handler
316 "id": data.config.get_agent_user_id_from_context(data.context),
320 "hwVersion":
"UNKNOWN_HW_VERSION",
321 "manufacturer":
"Home Assistant",
322 "model":
"Home Assistant",
323 "swVersion": __version__,
329 @HANDLERS.register("action.devices.REACHABLE_DEVICES")
331 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
333 """Handle action.devices.REACHABLE_DEVICES request.
335 https://developers.google.com/assistant/smarthome/develop/local#implement_the_reachable_devices_handler_hub_integrations_only
337 google_ids = {dev[
"id"]
for dev
in (data.devices
or [])}
341 entity.reachable_device_serialize()
343 if entity.entity_id
in google_ids
and entity.should_expose_local()
348 @HANDLERS.register("action.devices.PROXY_SELECTED")
350 hass: HomeAssistant, data: RequestData, payload: dict[str, Any]
352 """Handle action.devices.PROXY_SELECTED request.
354 When selected for local SDK.
360 """Return an empty sync response."""
362 "agentUserId": agent_user_id,
368 """Return a device turned off response."""
369 inputs: list = message.get(
"inputs")
371 if inputs
and inputs[0].
get(
"intent") ==
"action.devices.SYNC":
374 payload = {
"errorCode":
"deviceTurnedOff"}
377 "requestId": message.get(
"requestId"),
list[AlexaEntity] async_get_entities(HomeAssistant hass, AbstractConfig config)
web.Response get(self, web.Request request, str config_key)
dict[str, Any] async_redact_msg(dict[str, Any] msg, str agent_user_id)
def async_devices_sync_response(hass, config, agent_user_id)
dict[str, Any] async_devices_query(HomeAssistant hass, RequestData data, dict[str, Any] payload)
def create_sync_response(str agent_user_id, list devices)
dict[str, Any] async_devices_sync(HomeAssistant hass, RequestData data, dict[str, Any] payload)
dict[str, Any] async_devices_proxy_selected(HomeAssistant hass, RequestData data, dict[str, Any] payload)
def _process(hass, data, message)
dict[str, Any] async_devices_reachable(HomeAssistant hass, RequestData data, dict[str, Any] payload)
dict[str, Any] handle_devices_execute(HomeAssistant hass, RequestData data, dict[str, Any] payload)
def async_devices_query_response(hass, config, payload_devices)
def api_disabled_response(message, agent_user_id)
dict[str, Any] async_devices_identify(HomeAssistant hass, RequestData data, dict[str, Any] payload)
None async_devices_disconnect(HomeAssistant hass, RequestData data, dict[str, Any] payload)
def _entity_execute(entity, data, executions)
def async_handle_message(hass, config, agent_user_id, local_user_id, message, source)