Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions homeassistant/components/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,17 @@ async def async_initialize(self, cloud) -> None:
if not self.cloud.is_logged_in:
return

if self.alexa_config.should_report_state:
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.google_config.should_report_state:
self.google_config.async_enable_report_state()
if self.google_config.enabled:
self.google_config.async_enable_local_sdk()

if self.google_config.should_report_state:
self.google_config.async_enable_report_state()

async def cleanups(self) -> None:
"""Cleanup some stuff after logout."""
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/cloud/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
PREF_DISABLE_2FA = "disable_2fa"
PREF_ALIASES = "aliases"
PREF_SHOULD_EXPOSE = "should_expose"
PREF_GOOGLE_LOCAL_WEBHOOK_ID = "google_local_webhook_id"
DEFAULT_SHOULD_EXPOSE = True
DEFAULT_DISABLE_2FA = False
DEFAULT_ALEXA_REPORT_STATE = False
Expand Down
27 changes: 21 additions & 6 deletions homeassistant/components/cloud/google_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.google_report_state

@property
def local_sdk_webhook_id(self):
"""Return the local SDK webhook.

Return None to disable the local SDK.
"""
return self._prefs.google_local_webhook_id

@property
def local_sdk_user_id(self):
"""Return the user ID to be used for actions received via the local SDK."""
return self._prefs.cloud_user

def should_expose(self, state):
"""If a state object should be exposed."""
return self._should_expose_entity_id(state.entity_id)
Expand Down Expand Up @@ -131,17 +144,19 @@ async def _async_prefs_updated(self, prefs):
# State reporting is reported as a property on entities.
# So when we change it, we need to sync all entities.
await self.async_sync_entities()
return

# If entity prefs are the same or we have filter in config.yaml,
# don't sync.
if (
self._cur_entity_prefs is prefs.google_entity_configs
or not self._config["filter"].empty_filter
elif (
self._cur_entity_prefs is not prefs.google_entity_configs
and self._config["filter"].empty_filter
):
return
self.async_schedule_google_sync()

self.async_schedule_google_sync()
if self.enabled and not self.is_local_sdk_active:
self.async_enable_local_sdk()
elif not self.enabled and self.is_local_sdk_active:
self.async_disable_local_sdk()

async def _handle_entity_registry_updated(self, event):
"""Handle when entity registry updated."""
Expand Down
35 changes: 28 additions & 7 deletions homeassistant/components/cloud/prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
PREF_ALEXA_REPORT_STATE,
DEFAULT_ALEXA_REPORT_STATE,
PREF_GOOGLE_REPORT_STATE,
PREF_GOOGLE_LOCAL_WEBHOOK_ID,
DEFAULT_GOOGLE_REPORT_STATE,
InvalidTrustedNetworks,
InvalidTrustedProxies,
Expand Down Expand Up @@ -59,6 +60,14 @@ async def async_initialize(self):

self._prefs = prefs

if PREF_GOOGLE_LOCAL_WEBHOOK_ID not in self._prefs:
await self._save_prefs(
{
**self._prefs,
PREF_GOOGLE_LOCAL_WEBHOOK_ID: self._hass.components.webhook.async_generate_id(),
}
)

@callback
def async_listen_updates(self, listener):
"""Listen for updates to the preferences."""
Expand All @@ -79,6 +88,8 @@ async def async_update(
google_report_state=_UNDEF,
):
"""Update user preferences."""
prefs = {**self._prefs}

for key, value in (
(PREF_ENABLE_GOOGLE, google_enabled),
(PREF_ENABLE_ALEXA, alexa_enabled),
Expand All @@ -92,20 +103,17 @@ async def async_update(
(PREF_GOOGLE_REPORT_STATE, google_report_state),
):
if value is not _UNDEF:
self._prefs[key] = value
prefs[key] = value

if remote_enabled is True and self._has_local_trusted_network:
self._prefs[PREF_ENABLE_REMOTE] = False
prefs[PREF_ENABLE_REMOTE] = False
raise InvalidTrustedNetworks

if remote_enabled is True and self._has_local_trusted_proxies:
self._prefs[PREF_ENABLE_REMOTE] = False
prefs[PREF_ENABLE_REMOTE] = False
raise InvalidTrustedProxies

await self._store.async_save(self._prefs)

for listener in self._listeners:
self._hass.async_create_task(async_create_catching_coro(listener(self)))
await self._save_prefs(prefs)

async def async_update_google_entity_config(
self,
Expand Down Expand Up @@ -216,6 +224,11 @@ def google_entity_configs(self):
"""Return Google Entity configurations."""
return self._prefs.get(PREF_GOOGLE_ENTITY_CONFIGS, {})

@property
def google_local_webhook_id(self):
"""Return Google webhook ID to receive local messages."""
return self._prefs[PREF_GOOGLE_LOCAL_WEBHOOK_ID]

@property
def alexa_entity_configs(self):
"""Return Alexa Entity configurations."""
Expand Down Expand Up @@ -262,3 +275,11 @@ def _has_local_trusted_proxies(self) -> bool:
return True

return False

async def _save_prefs(self, prefs):
"""Save preferences to disk."""
self._prefs = prefs
await self._store.async_save(self._prefs)

for listener in self._listeners:
self._hass.async_create_task(async_create_catching_coro(listener(self)))
95 changes: 93 additions & 2 deletions homeassistant/components/google_assistant/helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
"""Helper classes for Google Assistant integration."""
from asyncio import gather
from collections.abc import Mapping
from typing import List
import logging
import pprint
from typing import List, Optional

from aiohttp.web import json_response

from homeassistant.core import Context, callback, HomeAssistant, State
from homeassistant.helpers.event import async_call_later
from homeassistant.components import webhook
from homeassistant.const import (
CONF_NAME,
STATE_UNAVAILABLE,
Expand All @@ -15,6 +20,7 @@

from . import trait
from .const import (
DOMAIN,
DOMAIN_TO_GOOGLE_TYPES,
CONF_ALIASES,
ERR_FUNCTION_NOT_SUPPORTED,
Expand All @@ -24,6 +30,7 @@
from .error import SmartHomeError

SYNC_DELAY = 15
_LOGGER = logging.getLogger(__name__)


class AbstractConfig:
Expand All @@ -35,6 +42,7 @@ def __init__(self, hass):
"""Initialize abstract config."""
self.hass = hass
self._google_sync_unsub = None
self._local_sdk_active = False

@property
def enabled(self):
Expand All @@ -61,12 +69,30 @@ def is_reporting_state(self):
"""Return if we're actively reporting states."""
return self._unsub_report_state is not None

@property
def is_local_sdk_active(self):
"""Return if we're actively accepting local messages."""
return self._local_sdk_active

@property
def should_report_state(self):
"""Return if states should be proactively reported."""
# pylint: disable=no-self-use
return False

@property
def local_sdk_webhook_id(self):
"""Return the local SDK webhook ID.

Return None to disable the local SDK.
"""
return None

@property
def local_sdk_user_id(self):
"""Return the user ID to be used for actions received via the local SDK."""
raise NotImplementedError

def should_expose(self, state) -> bool:
"""Return if entity should be exposed."""
raise NotImplementedError
Expand Down Expand Up @@ -131,15 +157,66 @@ async def async_deactivate_report_state(self):
Called when the user disconnects their account from Google.
"""

@callback
def async_enable_local_sdk(self):
"""Enable the local SDK."""
webhook_id = self.local_sdk_webhook_id

if webhook_id is None:
return

webhook.async_register(
self.hass, DOMAIN, "Local Support", webhook_id, self._handle_local_webhook
)

self._local_sdk_active = True

@callback
def async_disable_local_sdk(self):
"""Disable the local SDK."""
if not self._local_sdk_active:
return

webhook.async_unregister(self.hass, self.local_sdk_webhook_id)
self._local_sdk_active = False

async def _handle_local_webhook(self, hass, webhook_id, request):
"""Handle an incoming local SDK message."""
from . import smart_home

payload = await request.json()

if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Received local message:\n%s\n", pprint.pformat(payload))

if not self.enabled:
return json_response(smart_home.turned_off_response(payload))

result = await smart_home.async_handle_message(
self.hass, self, self.local_sdk_user_id, payload
)

if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Responding to local message:\n%s\n", pprint.pformat(result))

return json_response(result)


class RequestData:
"""Hold data associated with a particular request."""

def __init__(self, config: AbstractConfig, user_id: str, request_id: str):
def __init__(
self,
config: AbstractConfig,
user_id: str,
request_id: str,
devices: Optional[List[dict]],
):
"""Initialize the request data."""
self.config = config
self.request_id = request_id
self.context = Context(user_id=user_id)
self.devices = devices


def get_google_type(domain, device_class):
Expand Down Expand Up @@ -234,6 +311,15 @@ async def sync_serialize(self):
if aliases:
device["name"]["nicknames"] = aliases

if self.config.is_local_sdk_active:
device["otherDeviceIds"] = [{"deviceId": self.entity_id}]
device["customData"] = {
"webhookId": self.config.local_sdk_webhook_id,
"httpPort": self.hass.config.api.port,
"httpSSL": self.hass.config.api.use_ssl,
"proxyDeviceId": self.config.agent_user_id,
}

for trt in traits:
device["attributes"].update(trt.sync_attributes())

Expand Down Expand Up @@ -280,6 +366,11 @@ def query_serialize(self):

return attrs

@callback
def reachable_device_serialize(self):
"""Serialize entity for a REACHABLE_DEVICE response."""
return {"verificationId": self.entity_id}

async def execute(self, data, command_payload):
"""Execute a command.

Expand Down
Loading