Refactor handling of exposed entities for cloud Alexa and Google (#89877)

* Refactor handling of exposed entities for cloud Alexa

* Tweak WS API

* Validate assistant parameter

* Address some review comments

* Refactor handling of exposed entities for cloud Google

* Raise when attempting to expose an unknown entity

* Add tests

* Adjust cloud tests

* Allow getting expose new entities flag

* Test Alexa migration

* Test Google migration

* Add WS command cloud/google_assistant/entities/get

* Fix return value

* Update typing

* Address review comments

* Rename async_get_exposed_entities to async_get_assistant_settings
This commit is contained in:
Erik Montnemery 2023-04-06 19:09:45 +02:00 committed by GitHub
parent 0d84106947
commit 44c89a6b6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1607 additions and 305 deletions

View file

@ -1,5 +1,6 @@
"""The HTTP api to control the cloud integration."""
import asyncio
from collections.abc import Mapping
import dataclasses
from functools import wraps
from http import HTTPStatus
@ -22,22 +23,24 @@ from homeassistant.components.alexa import (
from homeassistant.components.google_assistant import helpers as google_helpers
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.location import async_detect_location_info
from .const import (
DOMAIN,
PREF_ALEXA_DEFAULT_EXPOSE,
PREF_ALEXA_REPORT_STATE,
PREF_DISABLE_2FA,
PREF_ENABLE_ALEXA,
PREF_ENABLE_GOOGLE,
PREF_GOOGLE_DEFAULT_EXPOSE,
PREF_GOOGLE_REPORT_STATE,
PREF_GOOGLE_SECURE_DEVICES_PIN,
PREF_TTS_DEFAULT_VOICE,
REQUEST_TIMEOUT,
)
from .google_config import CLOUD_GOOGLE
from .repairs import async_manage_legacy_subscription_issue
from .subscription import async_subscription_info
@ -66,11 +69,11 @@ async def async_setup(hass):
websocket_api.async_register_command(hass, websocket_remote_connect)
websocket_api.async_register_command(hass, websocket_remote_disconnect)
websocket_api.async_register_command(hass, google_assistant_get)
websocket_api.async_register_command(hass, google_assistant_list)
websocket_api.async_register_command(hass, google_assistant_update)
websocket_api.async_register_command(hass, alexa_list)
websocket_api.async_register_command(hass, alexa_update)
websocket_api.async_register_command(hass, alexa_sync)
websocket_api.async_register_command(hass, thingtalk_convert)
@ -350,8 +353,6 @@ async def websocket_subscription(
vol.Optional(PREF_ENABLE_ALEXA): bool,
vol.Optional(PREF_ALEXA_REPORT_STATE): bool,
vol.Optional(PREF_GOOGLE_REPORT_STATE): bool,
vol.Optional(PREF_ALEXA_DEFAULT_EXPOSE): [str],
vol.Optional(PREF_GOOGLE_DEFAULT_EXPOSE): [str],
vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str),
vol.Optional(PREF_TTS_DEFAULT_VOICE): vol.All(
vol.Coerce(tuple), vol.In(MAP_VOICE)
@ -523,6 +524,54 @@ async def websocket_remote_disconnect(
connection.send_result(msg["id"], await _account_data(hass, cloud))
@websocket_api.require_admin
@_require_cloud_login
@websocket_api.websocket_command(
{
"type": "cloud/google_assistant/entities/get",
"entity_id": str,
}
)
@websocket_api.async_response
@_ws_handle_cloud_errors
async def google_assistant_get(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Get data for a single google assistant entity."""
cloud = hass.data[DOMAIN]
gconf = await cloud.client.get_google_config()
entity_registry = er.async_get(hass)
entity_id: str = msg["entity_id"]
state = hass.states.get(entity_id)
if not entity_registry.async_is_registered(entity_id) or not state:
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_FOUND,
f"{entity_id} unknown or not in the entity registry",
)
return
entity = google_helpers.GoogleEntity(hass, gconf, state)
if entity_id in CLOUD_NEVER_EXPOSED_ENTITIES or not entity.is_supported():
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_SUPPORTED,
f"{entity_id} not supported by Google assistant",
)
return
result = {
"entity_id": entity.entity_id,
"traits": [trait.name for trait in entity.traits()],
"might_2fa": entity.might_2fa_traits(),
}
connection.send_result(msg["id"], result)
@websocket_api.require_admin
@_require_cloud_login
@websocket_api.websocket_command({"type": "cloud/google_assistant/entities"})
@ -536,11 +585,14 @@ async def google_assistant_list(
"""List all google assistant entities."""
cloud = hass.data[DOMAIN]
gconf = await cloud.client.get_google_config()
entity_registry = er.async_get(hass)
entities = google_helpers.async_get_entities(hass, gconf)
result = []
for entity in entities:
if not entity_registry.async_is_registered(entity.entity_id):
continue
result.append(
{
"entity_id": entity.entity_id,
@ -558,8 +610,7 @@ async def google_assistant_list(
{
"type": "cloud/google_assistant/entities/update",
"entity_id": str,
vol.Optional("should_expose"): vol.Any(None, bool),
vol.Optional("disable_2fa"): bool,
vol.Optional(PREF_DISABLE_2FA): bool,
}
)
@websocket_api.async_response
@ -569,17 +620,30 @@ async def google_assistant_update(
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Update google assistant config."""
cloud = hass.data[DOMAIN]
changes = dict(msg)
changes.pop("type")
changes.pop("id")
"""Update google assistant entity config."""
entity_registry = er.async_get(hass)
entity_id: str = msg["entity_id"]
await cloud.client.prefs.async_update_google_entity_config(**changes)
if not (registry_entry := entity_registry.async_get(entity_id)):
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_ALLOWED,
f"can't configure {entity_id}",
)
return
connection.send_result(
msg["id"], cloud.client.prefs.google_entity_configs.get(msg["entity_id"])
disable_2fa = msg[PREF_DISABLE_2FA]
assistant_options: Mapping[str, Any]
if (
assistant_options := registry_entry.options.get(CLOUD_GOOGLE, {})
) and assistant_options.get(PREF_DISABLE_2FA) == disable_2fa:
return
assistant_options = assistant_options | {PREF_DISABLE_2FA: disable_2fa}
entity_registry.async_update_entity_options(
entity_id, CLOUD_GOOGLE, assistant_options
)
connection.send_result(msg["id"])
@websocket_api.require_admin
@ -595,11 +659,14 @@ async def alexa_list(
"""List all alexa entities."""
cloud = hass.data[DOMAIN]
alexa_config = await cloud.client.get_alexa_config()
entity_registry = er.async_get(hass)
entities = alexa_entities.async_get_entities(hass, alexa_config)
result = []
for entity in entities:
if not entity_registry.async_is_registered(entity.entity_id):
continue
result.append(
{
"entity_id": entity.entity_id,
@ -611,35 +678,6 @@ async def alexa_list(
connection.send_result(msg["id"], result)
@websocket_api.require_admin
@_require_cloud_login
@websocket_api.websocket_command(
{
"type": "cloud/alexa/entities/update",
"entity_id": str,
vol.Optional("should_expose"): vol.Any(None, bool),
}
)
@websocket_api.async_response
@_ws_handle_cloud_errors
async def alexa_update(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Update alexa entity config."""
cloud = hass.data[DOMAIN]
changes = dict(msg)
changes.pop("type")
changes.pop("id")
await cloud.client.prefs.async_update_alexa_entity_config(**changes)
connection.send_result(
msg["id"], cloud.client.prefs.alexa_entity_configs.get(msg["entity_id"])
)
@websocket_api.require_admin
@_require_cloud_login
@websocket_api.websocket_command({"type": "cloud/alexa/sync"})