2019-03-11 19:21:20 +00:00
|
|
|
"""Interface implementation for cloud client."""
|
|
|
|
import asyncio
|
|
|
|
from pathlib import Path
|
|
|
|
from typing import Any, Dict
|
2019-06-17 20:50:01 +00:00
|
|
|
import logging
|
2019-03-11 19:21:20 +00:00
|
|
|
|
|
|
|
import aiohttp
|
|
|
|
from hass_nabucasa.client import CloudClient as Interface
|
|
|
|
|
2019-03-15 17:39:53 +00:00
|
|
|
from homeassistant.core import callback
|
2019-06-21 09:17:21 +00:00
|
|
|
from homeassistant.components.google_assistant import smart_home as ga
|
2019-03-11 19:21:20 +00:00
|
|
|
from homeassistant.helpers.typing import HomeAssistantType
|
2019-03-15 17:39:53 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
2019-03-11 19:21:20 +00:00
|
|
|
from homeassistant.util.aiohttp import MockRequest
|
2019-06-21 09:17:21 +00:00
|
|
|
from homeassistant.components.alexa import smart_home as alexa_sh
|
2019-03-11 19:21:20 +00:00
|
|
|
|
2019-06-21 09:17:21 +00:00
|
|
|
from . import utils, alexa_config, google_config
|
|
|
|
from .const import DISPATCHER_REMOTE_UPDATE
|
2019-03-11 19:21:20 +00:00
|
|
|
from .prefs import CloudPreferences
|
|
|
|
|
|
|
|
|
2019-06-17 20:50:01 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-06-13 18:58:08 +00:00
|
|
|
|
2019-03-11 19:21:20 +00:00
|
|
|
class CloudClient(Interface):
|
|
|
|
"""Interface class for Home Assistant Cloud."""
|
|
|
|
|
|
|
|
def __init__(self, hass: HomeAssistantType, prefs: CloudPreferences,
|
|
|
|
websession: aiohttp.ClientSession,
|
2019-06-21 09:17:21 +00:00
|
|
|
alexa_user_config: Dict[str, Any],
|
|
|
|
google_user_config: Dict[str, Any]):
|
2019-03-11 19:21:20 +00:00
|
|
|
"""Initialize client interface to Cloud."""
|
|
|
|
self._hass = hass
|
|
|
|
self._prefs = prefs
|
|
|
|
self._websession = websession
|
2019-06-21 09:17:21 +00:00
|
|
|
self.google_user_config = google_user_config
|
|
|
|
self.alexa_user_config = alexa_user_config
|
2019-06-17 20:50:01 +00:00
|
|
|
self._alexa_config = None
|
2019-03-11 19:21:20 +00:00
|
|
|
self._google_config = None
|
2019-06-17 20:50:01 +00:00
|
|
|
self.cloud = None
|
2019-03-11 19:21:20 +00:00
|
|
|
|
|
|
|
@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
|
|
|
|
|
2019-03-12 14:54:04 +00:00
|
|
|
@property
|
|
|
|
def remote_autostart(self) -> bool:
|
|
|
|
"""Return true if we want start a remote connection."""
|
|
|
|
return self._prefs.remote_enabled
|
|
|
|
|
2019-06-17 20:50:01 +00:00
|
|
|
@property
|
2019-06-21 09:17:21 +00:00
|
|
|
def alexa_config(self) -> alexa_config.AlexaConfig:
|
2019-06-17 20:50:01 +00:00
|
|
|
"""Return Alexa config."""
|
|
|
|
if self._alexa_config is None:
|
2019-06-21 09:17:21 +00:00
|
|
|
assert self.cloud is not None
|
|
|
|
self._alexa_config = alexa_config.AlexaConfig(
|
2019-06-17 20:50:01 +00:00
|
|
|
self._hass, self.alexa_user_config, self._prefs, self.cloud)
|
|
|
|
|
|
|
|
return self._alexa_config
|
|
|
|
|
2019-03-11 19:21:20 +00:00
|
|
|
@property
|
2019-06-21 09:17:21 +00:00
|
|
|
def google_config(self) -> google_config.CloudGoogleConfig:
|
2019-03-11 19:21:20 +00:00
|
|
|
"""Return Google config."""
|
|
|
|
if not self._google_config:
|
2019-06-21 09:17:21 +00:00
|
|
|
assert self.cloud is not None
|
|
|
|
self._google_config = google_config.CloudGoogleConfig(
|
|
|
|
self.google_user_config, self._prefs, self.cloud)
|
2019-04-23 20:13:00 +00:00
|
|
|
|
2019-03-11 19:21:20 +00:00
|
|
|
return self._google_config
|
|
|
|
|
2019-06-17 20:50:01 +00:00
|
|
|
async def async_initialize(self, cloud) -> None:
|
|
|
|
"""Initialize the client."""
|
|
|
|
self.cloud = cloud
|
|
|
|
|
|
|
|
if self.alexa_config.should_report_state and self.cloud.is_logged_in:
|
|
|
|
await self.alexa_config.async_enable_proactive_mode()
|
|
|
|
|
2019-03-11 19:21:20 +00:00
|
|
|
async def cleanups(self) -> None:
|
|
|
|
"""Cleanup some stuff after logout."""
|
|
|
|
self._google_config = None
|
|
|
|
|
2019-03-15 17:39:53 +00:00
|
|
|
@callback
|
|
|
|
def user_message(self, identifier: str, title: str, message: str) -> None:
|
2019-03-11 19:21:20 +00:00
|
|
|
"""Create a message for user to UI."""
|
|
|
|
self._hass.components.persistent_notification.async_create(
|
|
|
|
message, title, identifier
|
|
|
|
)
|
|
|
|
|
2019-03-15 17:39:53 +00:00
|
|
|
@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_"):
|
2019-03-15 17:39:53 +00:00
|
|
|
async_dispatcher_send(self._hass, DISPATCHER_REMOTE_UPDATE, data)
|
|
|
|
|
2019-03-11 19:21:20 +00:00
|
|
|
async def async_alexa_message(
|
|
|
|
self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
|
|
"""Process cloud alexa message to client."""
|
|
|
|
return await alexa_sh.async_handle_message(
|
|
|
|
self._hass, self.alexa_config, payload,
|
|
|
|
enabled=self._prefs.alexa_enabled
|
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2019-04-28 07:42:06 +00:00
|
|
|
return await ga.async_handle_message(
|
2019-03-14 23:18:31 +00:00
|
|
|
self._hass, self.google_config, self.prefs.cloud_user, payload
|
2019-03-11 19:21:20 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
async def async_webhook_message(
|
|
|
|
self, payload: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
|
|
"""Process cloud webhook message to client."""
|
|
|
|
cloudhook_id = payload['cloudhook_id']
|
|
|
|
|
|
|
|
found = None
|
|
|
|
for cloudhook in self._prefs.cloudhooks.values():
|
|
|
|
if cloudhook['cloudhook_id'] == cloudhook_id:
|
|
|
|
found = cloudhook
|
|
|
|
break
|
|
|
|
|
|
|
|
if found is None:
|
|
|
|
return {
|
|
|
|
'status': 200
|
|
|
|
}
|
|
|
|
|
|
|
|
request = MockRequest(
|
|
|
|
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(
|
|
|
|
found['webhook_id'], request)
|
|
|
|
|
|
|
|
response_dict = utils.aiohttp_serialize_response(response)
|
|
|
|
body = response_dict.get('body')
|
|
|
|
|
|
|
|
return {
|
|
|
|
'body': body,
|
|
|
|
'status': response_dict['status'],
|
|
|
|
'headers': {
|
|
|
|
'Content-Type': response.content_type
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|