core/homeassistant/components/cloud/client.py

202 lines
6.6 KiB
Python
Raw Normal View History

"""Interface implementation for cloud client."""
import asyncio
import logging
from pathlib import Path
from typing import Any, Dict
import aiohttp
from hass_nabucasa.client import CloudClient as Interface
2019-06-25 05:04:31 +00:00
from homeassistant.components.alexa import (
errors as alexa_errors,
smart_home as alexa_sh,
2019-06-25 05:04:31 +00:00
)
from homeassistant.components.google_assistant import const as gc, smart_home as ga
from homeassistant.core import Context, callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.util.aiohttp import MockRequest
from . import alexa_config, google_config, utils
from .const import DISPATCHER_REMOTE_UPDATE
from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__)
class CloudClient(Interface):
"""Interface class for Home Assistant Cloud."""
2019-07-31 19:25:30 +00:00
def __init__(
self,
hass: HomeAssistantType,
prefs: CloudPreferences,
websession: aiohttp.ClientSession,
alexa_user_config: Dict[str, Any],
google_user_config: Dict[str, Any],
):
"""Initialize client interface to Cloud."""
self._hass = hass
self._prefs = prefs
self._websession = websession
self.google_user_config = google_user_config
self.alexa_user_config = alexa_user_config
self._alexa_config = None
self._google_config = None
@property
def base_path(self) -> Path:
"""Return path to base dir."""
return Path(self._hass.config.config_dir)
@property
def prefs(self) -> CloudPreferences:
"""Return Cloud preferences."""
return self._prefs
@property
def loop(self) -> asyncio.BaseEventLoop:
"""Return client loop."""
return self._hass.loop
@property
def websession(self) -> aiohttp.ClientSession:
"""Return client session for aiohttp."""
return self._websession
@property
def aiohttp_runner(self) -> aiohttp.web.AppRunner:
"""Return client webinterface aiohttp application."""
return self._hass.http.runner
@property
def cloudhooks(self) -> Dict[str, Dict[str, str]]:
"""Return list of cloudhooks."""
return self._prefs.cloudhooks
@property
def remote_autostart(self) -> bool:
"""Return true if we want start a remote connection."""
return self._prefs.remote_enabled
@property
def alexa_config(self) -> alexa_config.AlexaConfig:
"""Return Alexa config."""
if self._alexa_config is None:
assert self.cloud is not None
self._alexa_config = alexa_config.AlexaConfig(
2019-07-31 19:25:30 +00:00
self._hass, self.alexa_user_config, self._prefs, self.cloud
)
return self._alexa_config
async def get_google_config(self) -> google_config.CloudGoogleConfig:
"""Return Google config."""
if not self._google_config:
assert self.cloud is not None
cloud_user = await self._prefs.get_cloud_user()
self._google_config = google_config.CloudGoogleConfig(
self._hass, self.google_user_config, cloud_user, self._prefs, self.cloud
2019-07-31 19:25:30 +00:00
)
await self._google_config.async_initialize()
2019-04-23 20:13:00 +00:00
return self._google_config
async def logged_in(self) -> None:
"""When user logs in."""
await self.prefs.async_set_username(self.cloud.username)
2019-06-25 05:04:31 +00:00
if self.alexa_config.enabled and self.alexa_config.should_report_state:
try:
await self.alexa_config.async_enable_proactive_mode()
except alexa_errors.NoTokenAvailable:
pass
if self._prefs.google_enabled:
gconf = await self.get_google_config()
gconf.async_enable_local_sdk()
if gconf.should_report_state:
gconf.async_enable_report_state()
async def cleanups(self) -> None:
"""Cleanup some stuff after logout."""
await self.prefs.async_set_username(None)
self._google_config = None
@callback
def user_message(self, identifier: str, title: str, message: str) -> None:
"""Create a message for user to UI."""
self._hass.components.persistent_notification.async_create(
message, title, identifier
)
@callback
def dispatcher_message(self, identifier: str, data: Any = None) -> None:
"""Match cloud notification to dispatcher."""
2019-03-15 18:11:59 +00:00
if identifier.startswith("remote_"):
async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data)
2019-07-31 19:25:30 +00:00
async def async_alexa_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
"""Process cloud alexa message to client."""
cloud_user = await self._prefs.get_cloud_user()
return await alexa_sh.async_handle_message(
self._hass,
self.alexa_config,
payload,
context=Context(user_id=cloud_user),
enabled=self._prefs.alexa_enabled,
)
2019-07-31 19:25:30 +00:00
async def async_google_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
"""Process cloud google message to client."""
if not self._prefs.google_enabled:
return ga.turned_off_response(payload)
gconf = await self.get_google_config()
return await ga.async_handle_message(
self._hass, gconf, gconf.cloud_user, payload, gc.SOURCE_CLOUD
)
2019-07-31 19:25:30 +00:00
async def async_webhook_message(self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
"""Process cloud webhook message to client."""
2019-07-31 19:25:30 +00:00
cloudhook_id = payload["cloudhook_id"]
found = None
for cloudhook in self._prefs.cloudhooks.values():
2019-07-31 19:25:30 +00:00
if cloudhook["cloudhook_id"] == cloudhook_id:
found = cloudhook
break
if found is None:
2019-07-31 19:25:30 +00:00
return {"status": 200}
request = MockRequest(
2019-07-31 19:25:30 +00:00
content=payload["body"].encode("utf-8"),
headers=payload["headers"],
method=payload["method"],
query_string=payload["query"],
)
response = await self._hass.components.webhook.async_handle_webhook(
2019-07-31 19:25:30 +00:00
found["webhook_id"], request
)
response_dict = utils.aiohttp_serialize_response(response)
2019-07-31 19:25:30 +00:00
body = response_dict.get("body")
return {
2019-07-31 19:25:30 +00:00
"body": body,
"status": response_dict["status"],
"headers": {"Content-Type": response.content_type},
}
2019-07-31 19:25:30 +00:00
async def async_cloudhooks_update(self, data: Dict[str, Dict[str, str]]) -> None:
"""Update local list of cloudhooks."""
await self._prefs.async_update(cloudhooks=data)