1 """The HTTP api to control the cloud integration."""
3 from __future__
import annotations
6 from collections.abc
import Awaitable, Callable, Coroutine, Mapping
7 from contextlib
import suppress
9 from functools
import wraps
10 from http
import HTTPStatus
12 from typing
import Any, Concatenate
15 from aiohttp
import web
17 from hass_nabucasa
import Cloud, auth, thingtalk
18 from hass_nabucasa.const
import STATE_DISCONNECTED
19 from hass_nabucasa.voice
import TTS_VOICES
20 import voluptuous
as vol
24 entities
as alexa_entities,
25 errors
as alexa_errors,
37 from .alexa_config
import entity_supported
as entity_supported_by_alexa
38 from .assist_pipeline
import async_create_cloud_pipeline
39 from .client
import CloudClient
42 PREF_ALEXA_REPORT_STATE,
45 PREF_ENABLE_CLOUD_ICE_SERVERS,
47 PREF_GOOGLE_REPORT_STATE,
48 PREF_GOOGLE_SECURE_DEVICES_PIN,
49 PREF_REMOTE_ALLOW_REMOTE_ENABLE,
50 PREF_TTS_DEFAULT_VOICE,
53 from .google_config
import CLOUD_GOOGLE
54 from .repairs
import async_manage_legacy_subscription_issue
55 from .subscription
import async_subscription_info
57 _LOGGER = logging.getLogger(__name__)
60 _CLOUD_ERRORS: dict[type[Exception], tuple[HTTPStatus, str]] = {
62 HTTPStatus.BAD_GATEWAY,
63 "Unable to reach the Home Assistant cloud.",
65 aiohttp.ClientError: (
66 HTTPStatus.INTERNAL_SERVER_ERROR,
67 "Error making internal request",
74 """Initialize the HTTP API."""
75 websocket_api.async_register_command(hass, websocket_cloud_remove_data)
76 websocket_api.async_register_command(hass, websocket_cloud_status)
77 websocket_api.async_register_command(hass, websocket_subscription)
78 websocket_api.async_register_command(hass, websocket_update_prefs)
79 websocket_api.async_register_command(hass, websocket_hook_create)
80 websocket_api.async_register_command(hass, websocket_hook_delete)
81 websocket_api.async_register_command(hass, websocket_remote_connect)
82 websocket_api.async_register_command(hass, websocket_remote_disconnect)
84 websocket_api.async_register_command(hass, google_assistant_get)
85 websocket_api.async_register_command(hass, google_assistant_list)
86 websocket_api.async_register_command(hass, google_assistant_update)
88 websocket_api.async_register_command(hass, alexa_get)
89 websocket_api.async_register_command(hass, alexa_list)
90 websocket_api.async_register_command(hass, alexa_sync)
92 websocket_api.async_register_command(hass, thingtalk_convert)
93 websocket_api.async_register_command(hass, tts_info)
95 hass.http.register_view(GoogleActionsSyncView)
96 hass.http.register_view(CloudLoginView)
97 hass.http.register_view(CloudLogoutView)
98 hass.http.register_view(CloudRegisterView)
99 hass.http.register_view(CloudResendConfirmView)
100 hass.http.register_view(CloudForgotPasswordView)
102 _CLOUD_ERRORS.update(
104 auth.UserNotFound: (HTTPStatus.BAD_REQUEST,
"User does not exist."),
105 auth.UserNotConfirmed: (HTTPStatus.BAD_REQUEST,
"Email not confirmed."),
107 HTTPStatus.BAD_REQUEST,
108 "An account with the given email already exists.",
110 auth.Unauthenticated: (HTTPStatus.UNAUTHORIZED,
"Authentication failed."),
111 auth.PasswordChangeRequired: (
112 HTTPStatus.BAD_REQUEST,
113 "Password change required.",
119 def _handle_cloud_errors[_HassViewT: HomeAssistantView, **_P](
121 Concatenate[_HassViewT, web.Request, _P], Awaitable[web.Response]
124 Concatenate[_HassViewT, web.Request, _P], Coroutine[Any, Any, web.Response]
126 """Webview decorator to handle auth errors."""
129 async
def error_handler(
130 view: _HassViewT, request: web.Request, *args: _P.args, **kwargs: _P.kwargs
132 """Handle exceptions that raise from the wrapped request handler."""
134 result = await handler(view, request, *args, **kwargs)
135 except Exception
as err:
137 return view.json_message(
138 msg, status_code=status, message_code=err.__class__.__name__.lower()
148 Coroutine[
None,
None,
None],
152 Coroutine[
None,
None,
None],
154 """Websocket decorator to handle auth errors."""
157 async
def error_handler(
162 """Handle exceptions that raise from the wrapped handler."""
164 return await handler(hass, connection, msg)
166 except Exception
as err:
168 connection.send_error(msg[
"id"],
str(err_status), err_msg)
174 """Process a cloud exception."""
175 err_info: tuple[HTTPStatus, str] |
None =
None
177 for err, value_info
in _CLOUD_ERRORS.items():
178 if isinstance(exc, err):
179 err_info = value_info
183 _LOGGER.exception(
"Unexpected error processing request for %s", where)
184 err_info = (HTTPStatus.BAD_GATEWAY, f
"Unexpected error: {exc}")
190 """Trigger a Google Actions Smart Home Sync."""
192 url =
"/api/cloud/google_actions/sync"
193 name =
"api:cloud:google_actions/sync"
196 @_handle_cloud_errors
197 async
def post(self, request: web.Request) -> web.Response:
198 """Trigger a Google Actions sync."""
199 hass = request.app[KEY_HASS]
200 cloud = hass.data[DATA_CLOUD]
201 gconf = await cloud.client.get_google_config()
202 status = await gconf.async_sync_entities(gconf.agent_user_id)
203 return self.json({}, status_code=status)
207 """Login to Home Assistant cloud."""
209 url =
"/api/cloud/login"
210 name =
"api:cloud:login"
213 @_handle_cloud_errors
214 @RequestDataValidator(
vol.Schema({vol.Required("email"): str, vol.Required(
"password"): str})
216 async
def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
217 """Handle login request."""
218 hass = request.app[KEY_HASS]
219 cloud = hass.data[DATA_CLOUD]
220 await cloud.login(data[
"email"], data[
"password"])
222 if "assist_pipeline" in hass.config.components:
225 new_cloud_pipeline_id =
None
226 return self.json({
"success":
True,
"cloud_pipeline": new_cloud_pipeline_id})
230 """Log out of the Home Assistant cloud."""
232 url =
"/api/cloud/logout"
233 name =
"api:cloud:logout"
236 @_handle_cloud_errors
237 async
def post(self, request: web.Request) -> web.Response:
238 """Handle logout request."""
239 hass = request.app[KEY_HASS]
240 cloud = hass.data[DATA_CLOUD]
242 async
with asyncio.timeout(REQUEST_TIMEOUT):
245 return self.json_message(
"ok")
249 """Register on the Home Assistant cloud."""
251 url =
"/api/cloud/register"
252 name =
"api:cloud:register"
255 @_handle_cloud_errors
256 @RequestDataValidator(
vol.Schema(
{
vol.Required("email"): str,
257 vol.Required(
"password"): vol.All(str, vol.Length(min=6)),
261 async
def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
262 """Handle registration request."""
263 hass = request.app[KEY_HASS]
264 cloud = hass.data[DATA_CLOUD]
266 client_metadata =
None
272 )
and location_info.country_code
is not None:
273 client_metadata = {
"NC_COUNTRY_CODE": location_info.country_code}
274 if location_info.region_code
is not None:
275 client_metadata[
"NC_REGION_CODE"] = location_info.region_code
276 if location_info.zip_code
is not None:
277 client_metadata[
"NC_ZIP_CODE"] = location_info.zip_code
279 async
with asyncio.timeout(REQUEST_TIMEOUT):
280 await cloud.auth.async_register(
283 client_metadata=client_metadata,
286 return self.json_message(
"ok")
290 """Resend email confirmation code."""
292 url =
"/api/cloud/resend_confirm"
293 name =
"api:cloud:resend_confirm"
296 @_handle_cloud_errors
297 @RequestDataValidator(vol.Schema({vol.Required("email"): str}))
298 async
def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
299 """Handle resending confirm email code request."""
300 hass = request.app[KEY_HASS]
301 cloud = hass.data[DATA_CLOUD]
303 async
with asyncio.timeout(REQUEST_TIMEOUT):
304 await cloud.auth.async_resend_email_confirm(data[
"email"])
306 return self.json_message(
"ok")
310 """View to start Forgot Password flow.."""
312 url =
"/api/cloud/forgot_password"
313 name =
"api:cloud:forgot_password"
316 @_handle_cloud_errors
317 @RequestDataValidator(vol.Schema({vol.Required("email"): str}))
318 async
def post(self, request: web.Request, data: dict[str, Any]) -> web.Response:
319 """Handle forgot password request."""
320 hass = request.app[KEY_HASS]
321 cloud = hass.data[DATA_CLOUD]
323 async
with asyncio.timeout(REQUEST_TIMEOUT):
324 await cloud.auth.async_forgot_password(data[
"email"])
326 return self.json_message(
"ok")
329 @websocket_api.require_admin
330 @websocket_api.websocket_command({vol.Required("type"):
"cloud/remove_data"})
331 @websocket_api.async_response
337 """Handle request for account info.
341 cloud = hass.data[DATA_CLOUD]
342 if cloud.is_logged_in:
343 connection.send_message(
344 websocket_api.error_message(
345 msg[
"id"],
"logged_in",
"Can't remove data when logged in."
350 await cloud.remove_data()
351 await cloud.client.prefs.async_erase_config()
353 connection.send_message(websocket_api.result_message(msg[
"id"]))
356 @websocket_api.websocket_command({vol.Required("type"):
"cloud/status"})
357 @websocket_api.async_response
363 """Handle request for account info.
367 cloud = hass.data[DATA_CLOUD]
368 connection.send_message(
369 websocket_api.result_message(msg[
"id"], await
_account_data(hass, cloud))
382 """Websocket decorator that requires cloud to be logged in."""
390 """Require to be logged into the cloud."""
391 cloud = hass.data[DATA_CLOUD]
392 if not cloud.is_logged_in:
393 connection.send_message(
394 websocket_api.error_message(
395 msg[
"id"],
"not_logged_in",
"You need to be logged in to the cloud."
400 handler(hass, connection, msg)
402 return with_cloud_auth
405 @_require_cloud_login
406 @websocket_api.websocket_command({vol.Required("type"):
"cloud/subscription"})
407 @websocket_api.async_response
413 """Handle request for account info."""
414 cloud = hass.data[DATA_CLOUD]
416 connection.send_error(
417 msg[
"id"],
"request_failed",
"Failed to request subscription"
421 connection.send_result(msg[
"id"], data)
426 """Validate language and voice."""
427 language, voice = value
428 if language
not in TTS_VOICES:
429 raise vol.Invalid(f
"Invalid language {language}")
430 if voice
not in TTS_VOICES[language]:
431 raise vol.Invalid(f
"Invalid voice {voice} for language {language}")
435 @_require_cloud_login
436 @websocket_api.websocket_command(
{
vol.Required("type"):
"cloud/update_prefs",
437 vol.Optional(PREF_ALEXA_REPORT_STATE): bool,
438 vol.Optional(PREF_ENABLE_ALEXA): bool,
439 vol.Optional(PREF_ENABLE_CLOUD_ICE_SERVERS): bool,
440 vol.Optional(PREF_ENABLE_GOOGLE): bool,
441 vol.Optional(PREF_GOOGLE_REPORT_STATE): bool,
442 vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(
None, str),
443 vol.Optional(PREF_REMOTE_ALLOW_REMOTE_ENABLE): bool,
444 vol.Optional(PREF_TTS_DEFAULT_VOICE): vol.All(
445 vol.Coerce(tuple), validate_language_voice
449 @websocket_api.async_response
455 """Handle request for account info."""
456 cloud = hass.data[DATA_CLOUD]
463 if changes.get(PREF_ALEXA_REPORT_STATE):
464 alexa_config = await cloud.client.get_alexa_config()
466 async
with asyncio.timeout(10):
467 await alexa_config.async_get_access_token()
469 connection.send_error(
470 msg[
"id"],
"alexa_timeout",
"Timeout validating Alexa access token."
473 except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
474 connection.send_error(
478 "Please go to the Alexa app and re-link the Home Assistant "
479 "skill and then try to enable state reporting."
482 await alexa_config.set_authorized(
False)
485 await alexa_config.set_authorized(
True)
487 await cloud.client.prefs.async_update(**changes)
489 connection.send_message(websocket_api.result_message(msg[
"id"]))
492 @_require_cloud_login
493 @websocket_api.websocket_command(
{
vol.Required("type"):
"cloud/cloudhook/create",
494 vol.Required(
"webhook_id"): str,
497 @websocket_api.async_response
498 @_ws_handle_cloud_errors
504 """Handle request for account info."""
505 cloud = hass.data[DATA_CLOUD]
506 hook = await cloud.cloudhooks.async_create(msg[
"webhook_id"],
False)
507 connection.send_message(websocket_api.result_message(msg[
"id"], hook))
510 @_require_cloud_login
511 @websocket_api.websocket_command(
{
vol.Required("type"):
"cloud/cloudhook/delete",
512 vol.Required(
"webhook_id"): str,
515 @websocket_api.async_response
516 @_ws_handle_cloud_errors
522 """Handle request for account info."""
523 cloud = hass.data[DATA_CLOUD]
524 await cloud.cloudhooks.async_delete(msg[
"webhook_id"])
525 connection.send_message(websocket_api.result_message(msg[
"id"]))
529 hass: HomeAssistant, cloud: Cloud[CloudClient]
531 """Generate the auth data JSON response."""
533 assert hass.config.api
534 if not cloud.is_logged_in:
537 "cloud": STATE_DISCONNECTED,
538 "http_use_ssl": hass.config.api.use_ssl,
541 claims = cloud.claims
542 client = cloud.client
543 remote = cloud.remote
545 alexa_config = await client.get_alexa_config()
546 google_config = await client.get_google_config()
549 if remote.certificate:
550 certificate = attr.asdict(remote.certificate)
554 if cloud.iot.last_disconnect_reason:
555 cloud_last_disconnect_reason = dataclasses.asdict(
556 cloud.iot.last_disconnect_reason
559 cloud_last_disconnect_reason =
None
562 "alexa_entities": client.alexa_user_config[
"filter"].config,
563 "alexa_registered": alexa_config.authorized,
564 "cloud": cloud.iot.state,
565 "cloud_last_disconnect_reason": cloud_last_disconnect_reason,
566 "email": claims[
"email"],
567 "google_entities": client.google_user_config[
"filter"].config,
568 "google_registered": google_config.has_registered_user_agent,
569 "google_local_connected": google_config.is_local_connected,
571 "prefs": client.prefs.as_dict(),
572 "remote_certificate": certificate,
573 "remote_certificate_status": remote.certificate_status,
574 "remote_connected": remote.is_connected,
575 "remote_domain": remote.instance_domain,
576 "http_use_ssl": hass.config.api.use_ssl,
577 "active_subscription":
not cloud.subscription_expired,
581 @websocket_api.require_admin
582 @_require_cloud_login
583 @websocket_api.websocket_command({"type": "cloud/remote/connect"})
584 @websocket_api.async_response
585 @_ws_handle_cloud_errors
591 """Handle request for connect remote."""
592 cloud = hass.data[DATA_CLOUD]
593 await cloud.client.prefs.async_update(remote_enabled=
True)
594 connection.send_result(msg[
"id"], await
_account_data(hass, cloud))
597 @websocket_api.require_admin
598 @_require_cloud_login
599 @websocket_api.websocket_command({"type": "cloud/remote/disconnect"})
600 @websocket_api.async_response
601 @_ws_handle_cloud_errors
607 """Handle request for disconnect remote."""
608 cloud = hass.data[DATA_CLOUD]
609 await cloud.client.prefs.async_update(remote_enabled=
False)
610 connection.send_result(msg[
"id"], await
_account_data(hass, cloud))
613 @websocket_api.require_admin
614 @_require_cloud_login
615 @websocket_api.websocket_command(
{
"type": "cloud/google_assistant/entities/get",
"entity_id": str,
}
)
616 @websocket_api.async_response
617 @_ws_handle_cloud_errors
623 """Get data for a single google assistant entity."""
624 cloud = hass.data[DATA_CLOUD]
625 gconf = await cloud.client.get_google_config()
626 entity_id: str = msg[
"entity_id"]
627 state = hass.states.get(entity_id)
630 connection.send_error(
632 websocket_api.ERR_NOT_FOUND,
633 f
"{entity_id} unknown",
637 entity = google_helpers.GoogleEntity(hass, gconf, state)
638 if entity_id
in CLOUD_NEVER_EXPOSED_ENTITIES
or not entity.is_supported():
639 connection.send_error(
641 websocket_api.ERR_NOT_SUPPORTED,
642 f
"{entity_id} not supported by Google assistant",
646 assistant_options: Mapping[str, Any] = {}
647 with suppress(HomeAssistantError, KeyError):
648 settings = exposed_entities.async_get_entity_settings(hass, entity_id)
649 assistant_options = settings[CLOUD_GOOGLE]
652 "entity_id": entity.entity_id,
653 "traits": [trait.name
for trait
in entity.traits()],
654 "might_2fa": entity.might_2fa_traits(),
655 PREF_DISABLE_2FA: assistant_options.get(PREF_DISABLE_2FA),
658 connection.send_result(msg[
"id"], result)
661 @websocket_api.require_admin
662 @_require_cloud_login
663 @websocket_api.websocket_command({"type": "cloud/google_assistant/entities"})
664 @websocket_api.async_response
665 @_ws_handle_cloud_errors
671 """List all google assistant entities."""
672 cloud = hass.data[DATA_CLOUD]
673 gconf = await cloud.client.get_google_config()
674 entities = google_helpers.async_get_entities(hass, gconf)
678 "entity_id": entity.entity_id,
679 "traits": [trait.name
for trait
in entity.traits()],
680 "might_2fa": entity.might_2fa_traits(),
682 for entity
in entities
685 connection.send_result(msg[
"id"], result)
688 @websocket_api.require_admin
689 @_require_cloud_login
690 @websocket_api.websocket_command(
{
"type": "cloud/google_assistant/entities/update",
"entity_id": str,
vol.Optional(PREF_DISABLE_2FA): bool,
693 @websocket_api.async_response
694 @_ws_handle_cloud_errors
700 """Update google assistant entity config."""
701 entity_id: str = msg[
"entity_id"]
703 assistant_options: Mapping[str, Any] = {}
704 with suppress(HomeAssistantError, KeyError):
705 settings = exposed_entities.async_get_entity_settings(hass, entity_id)
706 assistant_options = settings[CLOUD_GOOGLE]
708 disable_2fa = msg[PREF_DISABLE_2FA]
709 if assistant_options.get(PREF_DISABLE_2FA) == disable_2fa:
712 exposed_entities.async_set_assistant_option(
713 hass, CLOUD_GOOGLE, entity_id, PREF_DISABLE_2FA, disable_2fa
715 connection.send_result(msg[
"id"])
718 @websocket_api.require_admin
719 @_require_cloud_login
720 @websocket_api.websocket_command(
{
"type": "cloud/alexa/entities/get",
"entity_id": str,
}
)
721 @websocket_api.async_response
722 @_ws_handle_cloud_errors
728 """Get data for a single alexa entity."""
729 entity_id: str = msg[
"entity_id"]
731 if entity_id
in CLOUD_NEVER_EXPOSED_ENTITIES
or not entity_supported_by_alexa(
734 connection.send_error(
736 websocket_api.ERR_NOT_SUPPORTED,
737 f
"{entity_id} not supported by Alexa",
741 connection.send_result(msg[
"id"])
744 @websocket_api.require_admin
745 @_require_cloud_login
746 @websocket_api.websocket_command({"type": "cloud/alexa/entities"})
747 @websocket_api.async_response
748 @_ws_handle_cloud_errors
754 """List all alexa entities."""
755 cloud = hass.data[DATA_CLOUD]
756 alexa_config = await cloud.client.get_alexa_config()
757 entities = alexa_entities.async_get_entities(hass, alexa_config)
761 "entity_id": entity.entity_id,
762 "display_categories": entity.default_display_categories(),
763 "interfaces": [ifc.name()
for ifc
in entity.interfaces()],
765 for entity
in entities
768 connection.send_result(msg[
"id"], result)
771 @websocket_api.require_admin
772 @_require_cloud_login
773 @websocket_api.websocket_command({"type": "cloud/alexa/sync"})
774 @websocket_api.async_response
780 """Sync with Alexa."""
781 cloud = hass.data[DATA_CLOUD]
782 alexa_config = await cloud.client.get_alexa_config()
784 async
with asyncio.timeout(10):
786 success = await alexa_config.async_sync_entities()
787 except alexa_errors.NoTokenAvailable:
788 connection.send_error(
791 "Please go to the Alexa app and re-link the Home Assistant skill.",
796 connection.send_result(msg[
"id"])
798 connection.send_error(
799 msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
"Unknown error"
803 @websocket_api.websocket_command({"type": "cloud/thingtalk/convert", "query": str})
804 @websocket_api.async_response
810 """Convert a query."""
811 cloud = hass.data[DATA_CLOUD]
813 async
with asyncio.timeout(10):
815 connection.send_result(
816 msg[
"id"], await thingtalk.async_convert(cloud, msg[
"query"])
818 except thingtalk.ThingTalkConversionError
as err:
819 connection.send_error(msg[
"id"], websocket_api.ERR_UNKNOWN_ERROR,
str(err))
822 @websocket_api.websocket_command({"type": "cloud/tts/info"})
828 """Fetch available tts info."""
829 connection.send_result(
834 for language, voices
in TTS_VOICES.items()
839
web.Response post(self, web.Request request, dict[str, Any] data)
web.Response post(self, web.Request request, dict[str, Any] data)
web.Response post(self, web.Request request)
web.Response post(self, web.Request request, dict[str, Any] data)
web.Response post(self, web.Request request, dict[str, Any] data)
web.Response post(self, web.Request request)
str|None async_create_cloud_pipeline(HomeAssistant hass)
None tts_info(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
tuple[HTTPStatus, str] _process_cloud_exception(Exception exc, str where)
None websocket_cloud_status(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None thingtalk_convert(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Callable[[HomeAssistant, websocket_api.ActiveConnection, dict[str, Any]], None,] _require_cloud_login(Callable[[HomeAssistant, websocket_api.ActiveConnection, dict[str, Any]], None,] handler)
None alexa_list(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Callable[[HomeAssistant, websocket_api.ActiveConnection, dict[str, Any]], Coroutine[None, None, None],] _ws_handle_cloud_errors(Callable[[HomeAssistant, websocket_api.ActiveConnection, dict[str, Any]], Coroutine[None, None, None],] handler)
None websocket_hook_delete(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
tuple[str, str] validate_language_voice(tuple[str, str] value)
None websocket_hook_create(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_subscription(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None google_assistant_list(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None alexa_get(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_cloud_remove_data(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None async_setup(HomeAssistant hass)
dict[str, Any] _account_data(HomeAssistant hass, Cloud[CloudClient] cloud)
None google_assistant_get(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None google_assistant_update(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_remote_connect(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_update_prefs(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None alexa_sync(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None websocket_remote_disconnect(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None async_manage_legacy_subscription_issue(HomeAssistant hass, dict[str, Any] subscription_info)
dict[str, Any]|None async_subscription_info(Cloud[CloudClient] cloud)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
LocationInfo|None async_detect_location_info(aiohttp.ClientSession session)