Enable local fulfillment google assistant (#63218)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
f786237def
commit
25fe213f22
9 changed files with 480 additions and 79 deletions
|
@ -38,6 +38,7 @@ from .const import (
|
|||
NOT_EXPOSE_LOCAL,
|
||||
SOURCE_LOCAL,
|
||||
STORE_AGENT_USER_IDS,
|
||||
STORE_GOOGLE_LOCAL_WEBHOOK_ID,
|
||||
)
|
||||
from .error import SmartHomeError
|
||||
|
||||
|
@ -107,7 +108,7 @@ class AbstractConfig(ABC):
|
|||
async def async_initialize(self):
|
||||
"""Perform async initialization of config."""
|
||||
self._store = GoogleConfigStore(self.hass)
|
||||
await self._store.async_load()
|
||||
await self._store.async_initialize()
|
||||
|
||||
if not self.enabled:
|
||||
return
|
||||
|
@ -148,18 +149,22 @@ class AbstractConfig(ABC):
|
|||
"""Return if states should be proactively reported."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def local_sdk_webhook_id(self):
|
||||
"""Return the local SDK webhook ID.
|
||||
def get_local_agent_user_id(self, webhook_id):
|
||||
"""Return the user ID to be used for actions received via the local SDK.
|
||||
|
||||
Return None to disable the local SDK.
|
||||
Return None is no agent user id is found.
|
||||
"""
|
||||
return None
|
||||
found_agent_user_id = None
|
||||
for agent_user_id, agent_user_data in self._store.agent_user_ids.items():
|
||||
if agent_user_data[STORE_GOOGLE_LOCAL_WEBHOOK_ID] == webhook_id:
|
||||
found_agent_user_id = agent_user_id
|
||||
break
|
||||
|
||||
@property
|
||||
def local_sdk_user_id(self):
|
||||
"""Return the user ID to be used for actions received via the local SDK."""
|
||||
raise NotImplementedError
|
||||
return found_agent_user_id
|
||||
|
||||
def get_local_webhook_id(self, agent_user_id):
|
||||
"""Return the webhook ID to be used for actions for a given agent user id via the local SDK."""
|
||||
return self._store.agent_user_ids[agent_user_id][STORE_GOOGLE_LOCAL_WEBHOOK_ID]
|
||||
|
||||
@abstractmethod
|
||||
def get_agent_user_id(self, context):
|
||||
|
@ -267,22 +272,41 @@ class AbstractConfig(ABC):
|
|||
@callback
|
||||
def async_enable_local_sdk(self):
|
||||
"""Enable the local SDK."""
|
||||
if (webhook_id := self.local_sdk_webhook_id) is None:
|
||||
return
|
||||
setup_successfull = True
|
||||
setup_webhook_ids = []
|
||||
|
||||
try:
|
||||
webhook.async_register(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"Local Support",
|
||||
webhook_id,
|
||||
self._handle_local_webhook,
|
||||
for user_agent_id, _ in self._store.agent_user_ids.items():
|
||||
|
||||
if (webhook_id := self.get_local_webhook_id(user_agent_id)) is None:
|
||||
setup_successfull = False
|
||||
break
|
||||
|
||||
try:
|
||||
webhook.async_register(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
"Local Support for " + user_agent_id,
|
||||
webhook_id,
|
||||
self._handle_local_webhook,
|
||||
)
|
||||
setup_webhook_ids.append(webhook_id)
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"Webhook handler %s for agent user id %s is already defined!",
|
||||
webhook_id,
|
||||
user_agent_id,
|
||||
)
|
||||
setup_successfull = False
|
||||
break
|
||||
|
||||
if not setup_successfull:
|
||||
_LOGGER.warning(
|
||||
"Local fulfillment failed to setup, falling back to cloud fulfillment"
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.info("Webhook handler is already defined!")
|
||||
return
|
||||
for setup_webhook_id in setup_webhook_ids:
|
||||
webhook.async_unregister(self.hass, setup_webhook_id)
|
||||
|
||||
self._local_sdk_active = True
|
||||
self._local_sdk_active = setup_successfull
|
||||
|
||||
@callback
|
||||
def async_disable_local_sdk(self):
|
||||
|
@ -290,7 +314,11 @@ class AbstractConfig(ABC):
|
|||
if not self._local_sdk_active:
|
||||
return
|
||||
|
||||
webhook.async_unregister(self.hass, self.local_sdk_webhook_id)
|
||||
for agent_user_id in self._store.agent_user_ids:
|
||||
webhook.async_unregister(
|
||||
self.hass, self.get_local_webhook_id(agent_user_id)
|
||||
)
|
||||
|
||||
self._local_sdk_active = False
|
||||
|
||||
async def _handle_local_webhook(self, hass, webhook_id, request):
|
||||
|
@ -307,8 +335,23 @@ class AbstractConfig(ABC):
|
|||
if not self.enabled:
|
||||
return json_response(smart_home.turned_off_response(payload))
|
||||
|
||||
if (agent_user_id := self.get_local_agent_user_id(webhook_id)) is None:
|
||||
# No agent user linked to this webhook, means that the user has somehow unregistered
|
||||
# removing webhook and stopping processing of this request.
|
||||
_LOGGER.error(
|
||||
"Cannot process request for webhook %s as no linked agent user is found:\n%s\n",
|
||||
webhook_id,
|
||||
pprint.pformat(payload),
|
||||
)
|
||||
webhook.async_unregister(self.hass, webhook_id)
|
||||
return None
|
||||
|
||||
result = await smart_home.async_handle_message(
|
||||
self.hass, self, self.local_sdk_user_id, payload, SOURCE_LOCAL
|
||||
self.hass,
|
||||
self,
|
||||
agent_user_id,
|
||||
payload,
|
||||
SOURCE_LOCAL,
|
||||
)
|
||||
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
|
@ -327,7 +370,32 @@ class GoogleConfigStore:
|
|||
"""Initialize a configuration store."""
|
||||
self._hass = hass
|
||||
self._store = Store(hass, self._STORAGE_VERSION, self._STORAGE_KEY)
|
||||
self._data = {STORE_AGENT_USER_IDS: {}}
|
||||
self._data = None
|
||||
|
||||
async def async_initialize(self):
|
||||
"""Finish initializing the ConfigStore."""
|
||||
should_save_data = False
|
||||
if (data := await self._store.async_load()) is None:
|
||||
# if the store is not found create an empty one
|
||||
# Note that the first request is always a cloud request,
|
||||
# and that will store the correct agent user id to be used for local requests
|
||||
data = {
|
||||
STORE_AGENT_USER_IDS: {},
|
||||
}
|
||||
should_save_data = True
|
||||
|
||||
for agent_user_id, agent_user_data in data[STORE_AGENT_USER_IDS].items():
|
||||
if STORE_GOOGLE_LOCAL_WEBHOOK_ID not in agent_user_data:
|
||||
data[STORE_AGENT_USER_IDS][agent_user_id] = {
|
||||
**agent_user_data,
|
||||
STORE_GOOGLE_LOCAL_WEBHOOK_ID: webhook.async_generate_id(),
|
||||
}
|
||||
should_save_data = True
|
||||
|
||||
if should_save_data:
|
||||
await self._store.async_save(data)
|
||||
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def agent_user_ids(self):
|
||||
|
@ -338,7 +406,9 @@ class GoogleConfigStore:
|
|||
def add_agent_user_id(self, agent_user_id):
|
||||
"""Add an agent user id to store."""
|
||||
if agent_user_id not in self._data[STORE_AGENT_USER_IDS]:
|
||||
self._data[STORE_AGENT_USER_IDS][agent_user_id] = {}
|
||||
self._data[STORE_AGENT_USER_IDS][agent_user_id] = {
|
||||
STORE_GOOGLE_LOCAL_WEBHOOK_ID: webhook.async_generate_id(),
|
||||
}
|
||||
self._store.async_delay_save(lambda: self._data, 1.0)
|
||||
|
||||
@callback
|
||||
|
@ -348,11 +418,6 @@ class GoogleConfigStore:
|
|||
self._data[STORE_AGENT_USER_IDS].pop(agent_user_id, None)
|
||||
self._store.async_delay_save(lambda: self._data, 1.0)
|
||||
|
||||
async def async_load(self):
|
||||
"""Store current configuration to disk."""
|
||||
if data := await self._store.async_load():
|
||||
self._data = data
|
||||
|
||||
|
||||
class RequestData:
|
||||
"""Hold data associated with a particular request."""
|
||||
|
@ -507,7 +572,7 @@ class GoogleEntity:
|
|||
if self.config.is_local_sdk_active and self.should_expose_local():
|
||||
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
|
||||
device["customData"] = {
|
||||
"webhookId": self.config.local_sdk_webhook_id,
|
||||
"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(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue