Cloud: Add Alexa report state (#24536)

* Cloud: Add Alexa report state

* Lint

* Lint

* Only track state changes when we are logged in
This commit is contained in:
Paulus Schoutsen 2019-06-17 13:50:01 -07:00 committed by GitHub
parent 5ab1996d3f
commit a02b69db38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 207 additions and 33 deletions

View file

@ -1,14 +1,26 @@
"""Config helpers for Alexa.""" """Config helpers for Alexa."""
from .state_report import async_enable_proactive_mode
class AbstractConfig: class AbstractConfig:
"""Hold the configuration for Alexa.""" """Hold the configuration for Alexa."""
_unsub_proactive_report = None
def __init__(self, hass):
"""Initialize abstract config."""
self.hass = hass
@property @property
def supports_auth(self): def supports_auth(self):
"""Return if config supports auth.""" """Return if config supports auth."""
return False return False
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return False
@property @property
def endpoint(self): def endpoint(self):
"""Endpoint for report state.""" """Endpoint for report state."""
@ -19,6 +31,30 @@ class AbstractConfig:
"""Return entity config.""" """Return entity config."""
return {} return {}
@property
def is_reporting_states(self):
"""Return if proactive mode is enabled."""
return self._unsub_proactive_report is not None
async def async_enable_proactive_mode(self):
"""Enable proactive mode."""
if self._unsub_proactive_report is None:
self._unsub_proactive_report = self.hass.async_create_task(
async_enable_proactive_mode(self.hass, self)
)
resp = await self._unsub_proactive_report
# Failed to start reporting.
if resp is None:
self._unsub_proactive_report = None
async def async_disable_proactive_mode(self):
"""Disable proactive mode."""
unsub_func = await self._unsub_proactive_report
if unsub_func:
unsub_func()
self._unsub_proactive_report = None
def should_expose(self, entity_id): def should_expose(self, entity_id):
"""If an entity should be exposed.""" """If an entity should be exposed."""
# pylint: disable=no-self-use # pylint: disable=no-self-use

View file

@ -87,7 +87,9 @@ async def async_api_accept_grant(hass, config, directive, context):
if config.supports_auth: if config.supports_auth:
await config.async_accept_grant(auth_code) await config.async_accept_grant(auth_code)
await async_enable_proactive_mode(hass, config)
if config.should_report_state:
await async_enable_proactive_mode(hass, config)
return directive.response( return directive.response(
name='AcceptGrant.Response', name='AcceptGrant.Response',

View file

@ -25,6 +25,7 @@ class AlexaConfig(AbstractConfig):
def __init__(self, hass, config): def __init__(self, hass, config):
"""Initialize Alexa config.""" """Initialize Alexa config."""
super().__init__(hass)
self._config = config self._config = config
if config.get(CONF_CLIENT_ID) and config.get(CONF_CLIENT_SECRET): if config.get(CONF_CLIENT_ID) and config.get(CONF_CLIENT_SECRET):
@ -38,6 +39,11 @@ class AlexaConfig(AbstractConfig):
"""Return if config supports auth.""" """Return if config supports auth."""
return self._auth is not None return self._auth is not None
@property
def should_report_state(self):
"""Return if we should proactively report states."""
return self._auth is not None
@property @property
def endpoint(self): def endpoint(self):
"""Endpoint for report state.""" """Endpoint for report state."""
@ -73,7 +79,7 @@ async def async_setup(hass, config):
smart_home_config = AlexaConfig(hass, config) smart_home_config = AlexaConfig(hass, config)
hass.http.register_view(SmartHomeView(smart_home_config)) hass.http.register_view(SmartHomeView(smart_home_config))
if smart_home_config.supports_auth: if smart_home_config.should_report_state:
await async_enable_proactive_mode(hass, smart_home_config) await async_enable_proactive_mode(hass, smart_home_config)

View file

@ -21,24 +21,23 @@ async def async_enable_proactive_mode(hass, smart_home_config):
Proactive mode makes this component report state changes to Alexa. Proactive mode makes this component report state changes to Alexa.
""" """
if smart_home_config.async_get_access_token is None:
# no function to call to get token
return
if await smart_home_config.async_get_access_token() is None: if await smart_home_config.async_get_access_token() is None:
# not ready yet # not ready yet
return return
async def async_entity_state_listener(changed_entity, old_state, async def async_entity_state_listener(changed_entity, old_state,
new_state): new_state):
if not smart_home_config.should_expose(changed_entity): if not new_state:
_LOGGER.debug("Not exposing %s because filtered by config",
changed_entity)
return return
if new_state.domain not in ENTITY_ADAPTERS: if new_state.domain not in ENTITY_ADAPTERS:
return return
if not smart_home_config.should_expose(changed_entity):
_LOGGER.debug("Not exposing %s because filtered by config",
changed_entity)
return
alexa_changed_entity = \ alexa_changed_entity = \
ENTITY_ADAPTERS[new_state.domain](hass, smart_home_config, ENTITY_ADAPTERS[new_state.domain](hass, smart_home_config,
new_state) new_state)
@ -49,7 +48,7 @@ async def async_enable_proactive_mode(hass, smart_home_config):
alexa_changed_entity) alexa_changed_entity)
return return
hass.helpers.event.async_track_state_change( return hass.helpers.event.async_track_state_change(
MATCH_ALL, async_entity_state_listener MATCH_ALL, async_entity_state_listener
) )
@ -94,7 +93,7 @@ async def async_send_changereport_message(hass, config, alexa_entity):
allow_redirects=True) allow_redirects=True)
except (asyncio.TimeoutError, aiohttp.ClientError): except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout calling LWA to get auth token.") _LOGGER.error("Timeout sending report to Alexa.")
return None return None
response_text = await response.text() response_text = await response.text()

View file

@ -2,8 +2,11 @@
import asyncio import asyncio
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict
from datetime import timedelta
import logging
import aiohttp import aiohttp
from hass_nabucasa import cloud_api
from hass_nabucasa.client import CloudClient as Interface from hass_nabucasa.client import CloudClient as Interface
from homeassistant.core import callback from homeassistant.core import callback
@ -17,22 +20,41 @@ from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.util.aiohttp import MockRequest from homeassistant.util.aiohttp import MockRequest
from homeassistant.util.dt import utcnow
from . import utils from . import utils
from .const import ( from .const import (
CONF_ENTITY_CONFIG, CONF_FILTER, DOMAIN, DISPATCHER_REMOTE_UPDATE, CONF_ENTITY_CONFIG, CONF_FILTER, DOMAIN, DISPATCHER_REMOTE_UPDATE,
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE, PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE,
PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA) PREF_DISABLE_2FA, DEFAULT_DISABLE_2FA, RequireRelink)
from .prefs import CloudPreferences from .prefs import CloudPreferences
_LOGGER = logging.getLogger(__name__)
class AlexaConfig(alexa_config.AbstractConfig): class AlexaConfig(alexa_config.AbstractConfig):
"""Alexa Configuration.""" """Alexa Configuration."""
def __init__(self, config, prefs): def __init__(self, hass, config, prefs, cloud):
"""Initialize the Alexa config.""" """Initialize the Alexa config."""
super().__init__(hass)
self._config = config self._config = config
self._prefs = prefs self._prefs = prefs
self._cloud = cloud
self._token = None
self._token_valid = None
prefs.async_listen_updates(self.async_prefs_updated)
@property
def supports_auth(self):
"""Return if config supports auth."""
return True
@property
def should_report_state(self):
"""Return if states should be proactively reported."""
return self._prefs.alexa_report_state
@property @property
def endpoint(self): def endpoint(self):
@ -57,6 +79,34 @@ class AlexaConfig(alexa_config.AbstractConfig):
return entity_config.get( return entity_config.get(
PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE) PREF_SHOULD_EXPOSE, DEFAULT_SHOULD_EXPOSE)
async def async_get_access_token(self):
"""Get an access token."""
if self._token_valid is not None and self._token_valid < utcnow():
return self._token
resp = await cloud_api.async_alexa_access_token(self._cloud)
body = await resp.json()
if resp.status == 400:
if body['reason'] in ('RefreshTokenNotFound', 'UnknownRegion'):
raise RequireRelink
return None
self._token = body['access_token']
self._token_valid = utcnow() + timedelta(seconds=body['expires_in'])
return self._token
async def async_prefs_updated(self, prefs):
"""Handle updated preferences."""
if self.should_report_state == self.is_reporting_states:
return
if self.should_report_state:
await self.async_enable_proactive_mode()
else:
await self.async_disable_proactive_mode()
class CloudClient(Interface): class CloudClient(Interface):
"""Interface class for Home Assistant Cloud.""" """Interface class for Home Assistant Cloud."""
@ -70,9 +120,9 @@ class CloudClient(Interface):
self._websession = websession self._websession = websession
self.google_user_config = google_config self.google_user_config = google_config
self.alexa_user_config = alexa_cfg self.alexa_user_config = alexa_cfg
self._alexa_config = None
self.alexa_config = AlexaConfig(alexa_cfg, prefs)
self._google_config = None self._google_config = None
self.cloud = None
@property @property
def base_path(self) -> Path: def base_path(self) -> Path:
@ -109,6 +159,15 @@ class CloudClient(Interface):
"""Return true if we want start a remote connection.""" """Return true if we want start a remote connection."""
return self._prefs.remote_enabled return self._prefs.remote_enabled
@property
def alexa_config(self) -> AlexaConfig:
"""Return Alexa config."""
if self._alexa_config is None:
self._alexa_config = AlexaConfig(
self._hass, self.alexa_user_config, self._prefs, self.cloud)
return self._alexa_config
@property @property
def google_config(self) -> ga_h.Config: def google_config(self) -> ga_h.Config:
"""Return Google config.""" """Return Google config."""
@ -151,6 +210,13 @@ class CloudClient(Interface):
return self._google_config return self._google_config
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()
async def cleanups(self) -> None: async def cleanups(self) -> None:
"""Cleanup some stuff after logout.""" """Cleanup some stuff after logout."""
self._google_config = None self._google_config = None

View file

@ -10,12 +10,14 @@ PREF_CLOUDHOOKS = 'cloudhooks'
PREF_CLOUD_USER = 'cloud_user' PREF_CLOUD_USER = 'cloud_user'
PREF_GOOGLE_ENTITY_CONFIGS = 'google_entity_configs' PREF_GOOGLE_ENTITY_CONFIGS = 'google_entity_configs'
PREF_ALEXA_ENTITY_CONFIGS = 'alexa_entity_configs' PREF_ALEXA_ENTITY_CONFIGS = 'alexa_entity_configs'
PREF_ALEXA_REPORT_STATE = 'alexa_report_state'
PREF_OVERRIDE_NAME = 'override_name' PREF_OVERRIDE_NAME = 'override_name'
PREF_DISABLE_2FA = 'disable_2fa' PREF_DISABLE_2FA = 'disable_2fa'
PREF_ALIASES = 'aliases' PREF_ALIASES = 'aliases'
PREF_SHOULD_EXPOSE = 'should_expose' PREF_SHOULD_EXPOSE = 'should_expose'
DEFAULT_SHOULD_EXPOSE = True DEFAULT_SHOULD_EXPOSE = True
DEFAULT_DISABLE_2FA = False DEFAULT_DISABLE_2FA = False
DEFAULT_ALEXA_REPORT_STATE = False
CONF_ALEXA = 'alexa' CONF_ALEXA = 'alexa'
CONF_ALIASES = 'aliases' CONF_ALIASES = 'aliases'
@ -43,3 +45,7 @@ class InvalidTrustedNetworks(Exception):
class InvalidTrustedProxies(Exception): class InvalidTrustedProxies(Exception):
"""Raised when invalid trusted proxies config.""" """Raised when invalid trusted proxies config."""
class RequireRelink(Exception):
"""The skill needs to be relinked."""

View file

@ -19,7 +19,7 @@ from homeassistant.components.google_assistant import helpers as google_helpers
from .const import ( from .const import (
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks, PREF_GOOGLE_SECURE_DEVICES_PIN, InvalidTrustedNetworks,
InvalidTrustedProxies) InvalidTrustedProxies, PREF_ALEXA_REPORT_STATE)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -363,6 +363,7 @@ async def websocket_subscription(hass, connection, msg):
vol.Required('type'): 'cloud/update_prefs', vol.Required('type'): 'cloud/update_prefs',
vol.Optional(PREF_ENABLE_GOOGLE): bool, vol.Optional(PREF_ENABLE_GOOGLE): bool,
vol.Optional(PREF_ENABLE_ALEXA): bool, vol.Optional(PREF_ENABLE_ALEXA): bool,
vol.Optional(PREF_ALEXA_REPORT_STATE): bool,
vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str), vol.Optional(PREF_GOOGLE_SECURE_DEVICES_PIN): vol.Any(None, str),
}) })
async def websocket_update_prefs(hass, connection, msg): async def websocket_update_prefs(hass, connection, msg):
@ -424,7 +425,6 @@ def _account_data(cloud):
'prefs': client.prefs.as_dict(), 'prefs': client.prefs.as_dict(),
'google_entities': client.google_user_config['filter'].config, 'google_entities': client.google_user_config['filter'].config,
'alexa_entities': client.alexa_user_config['filter'].config, 'alexa_entities': client.alexa_user_config['filter'].config,
'alexa_domains': list(alexa_entities.ENTITY_ADAPTERS),
'remote_domain': remote.instance_domain, 'remote_domain': remote.instance_domain,
'remote_connected': remote.is_connected, 'remote_connected': remote.is_connected,
'remote_certificate': certificate, 'remote_certificate': certificate,

View file

@ -3,7 +3,7 @@
"name": "Cloud", "name": "Cloud",
"documentation": "https://www.home-assistant.io/components/cloud", "documentation": "https://www.home-assistant.io/components/cloud",
"requirements": [ "requirements": [
"hass-nabucasa==0.14" "hass-nabucasa==0.15"
], ],
"dependencies": [ "dependencies": [
"http", "http",

View file

@ -1,11 +1,15 @@
"""Preference management for cloud.""" """Preference management for cloud."""
from ipaddress import ip_address from ipaddress import ip_address
from homeassistant.core import callback
from homeassistant.util.logging import async_create_catching_coro
from .const import ( from .const import (
DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE, DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE, PREF_ENABLE_REMOTE,
PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER, PREF_GOOGLE_SECURE_DEVICES_PIN, PREF_CLOUDHOOKS, PREF_CLOUD_USER,
PREF_GOOGLE_ENTITY_CONFIGS, PREF_OVERRIDE_NAME, PREF_DISABLE_2FA, PREF_GOOGLE_ENTITY_CONFIGS, PREF_OVERRIDE_NAME, PREF_DISABLE_2FA,
PREF_ALIASES, PREF_SHOULD_EXPOSE, PREF_ALEXA_ENTITY_CONFIGS, PREF_ALIASES, PREF_SHOULD_EXPOSE, PREF_ALEXA_ENTITY_CONFIGS,
PREF_ALEXA_REPORT_STATE, DEFAULT_ALEXA_REPORT_STATE,
InvalidTrustedNetworks, InvalidTrustedProxies) InvalidTrustedNetworks, InvalidTrustedProxies)
STORAGE_KEY = DOMAIN STORAGE_KEY = DOMAIN
@ -21,6 +25,7 @@ class CloudPreferences:
self._hass = hass self._hass = hass
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._prefs = None self._prefs = None
self._listeners = []
async def async_initialize(self): async def async_initialize(self):
"""Finish initializing the preferences.""" """Finish initializing the preferences."""
@ -40,11 +45,17 @@ class CloudPreferences:
self._prefs = prefs self._prefs = prefs
@callback
def async_listen_updates(self, listener):
"""Listen for updates to the preferences."""
self._listeners.append(listener)
async def async_update(self, *, google_enabled=_UNDEF, async def async_update(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF, remote_enabled=_UNDEF, alexa_enabled=_UNDEF, remote_enabled=_UNDEF,
google_secure_devices_pin=_UNDEF, cloudhooks=_UNDEF, google_secure_devices_pin=_UNDEF, cloudhooks=_UNDEF,
cloud_user=_UNDEF, google_entity_configs=_UNDEF, cloud_user=_UNDEF, google_entity_configs=_UNDEF,
alexa_entity_configs=_UNDEF): alexa_entity_configs=_UNDEF,
alexa_report_state=_UNDEF):
"""Update user preferences.""" """Update user preferences."""
for key, value in ( for key, value in (
(PREF_ENABLE_GOOGLE, google_enabled), (PREF_ENABLE_GOOGLE, google_enabled),
@ -55,18 +66,26 @@ class CloudPreferences:
(PREF_CLOUD_USER, cloud_user), (PREF_CLOUD_USER, cloud_user),
(PREF_GOOGLE_ENTITY_CONFIGS, google_entity_configs), (PREF_GOOGLE_ENTITY_CONFIGS, google_entity_configs),
(PREF_ALEXA_ENTITY_CONFIGS, alexa_entity_configs), (PREF_ALEXA_ENTITY_CONFIGS, alexa_entity_configs),
(PREF_ALEXA_REPORT_STATE, alexa_report_state),
): ):
if value is not _UNDEF: if value is not _UNDEF:
self._prefs[key] = value self._prefs[key] = value
if remote_enabled is True and self._has_local_trusted_network: if remote_enabled is True and self._has_local_trusted_network:
self._prefs[PREF_ENABLE_REMOTE] = False
raise InvalidTrustedNetworks raise InvalidTrustedNetworks
if remote_enabled is True and self._has_local_trusted_proxies: if remote_enabled is True and self._has_local_trusted_proxies:
self._prefs[PREF_ENABLE_REMOTE] = False
raise InvalidTrustedProxies raise InvalidTrustedProxies
await self._store.async_save(self._prefs) await self._store.async_save(self._prefs)
for listener in self._listeners:
self._hass.async_create_task(
async_create_catching_coro(listener(self))
)
async def async_update_google_entity_config( async def async_update_google_entity_config(
self, *, entity_id, override_name=_UNDEF, disable_2fa=_UNDEF, self, *, entity_id, override_name=_UNDEF, disable_2fa=_UNDEF,
aliases=_UNDEF, should_expose=_UNDEF): aliases=_UNDEF, should_expose=_UNDEF):
@ -134,6 +153,7 @@ class CloudPreferences:
PREF_GOOGLE_SECURE_DEVICES_PIN: self.google_secure_devices_pin, PREF_GOOGLE_SECURE_DEVICES_PIN: self.google_secure_devices_pin,
PREF_GOOGLE_ENTITY_CONFIGS: self.google_entity_configs, PREF_GOOGLE_ENTITY_CONFIGS: self.google_entity_configs,
PREF_ALEXA_ENTITY_CONFIGS: self.alexa_entity_configs, PREF_ALEXA_ENTITY_CONFIGS: self.alexa_entity_configs,
PREF_ALEXA_REPORT_STATE: self.alexa_report_state,
PREF_CLOUDHOOKS: self.cloudhooks, PREF_CLOUDHOOKS: self.cloudhooks,
PREF_CLOUD_USER: self.cloud_user, PREF_CLOUD_USER: self.cloud_user,
} }
@ -156,6 +176,12 @@ class CloudPreferences:
"""Return if Alexa is enabled.""" """Return if Alexa is enabled."""
return self._prefs[PREF_ENABLE_ALEXA] return self._prefs[PREF_ENABLE_ALEXA]
@property
def alexa_report_state(self):
"""Return if Alexa report state is enabled."""
return self._prefs.get(PREF_ALEXA_REPORT_STATE,
DEFAULT_ALEXA_REPORT_STATE)
@property @property
def google_enabled(self): def google_enabled(self):
"""Return if Google is enabled.""" """Return if Google is enabled."""

View file

@ -9,7 +9,7 @@ bcrypt==3.1.6
certifi>=2018.04.16 certifi>=2018.04.16
cryptography==2.6.1 cryptography==2.6.1
distro==1.4.0 distro==1.4.0
hass-nabucasa==0.14 hass-nabucasa==0.15
home-assistant-frontend==20190614.0 home-assistant-frontend==20190614.0
importlib-metadata==0.15 importlib-metadata==0.15
jinja2>=2.10 jinja2>=2.10

View file

@ -562,7 +562,7 @@ habitipy==0.2.0
hangups==0.4.9 hangups==0.4.9
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.14 hass-nabucasa==0.15
# homeassistant.components.mqtt # homeassistant.components.mqtt
hbmqtt==0.9.4 hbmqtt==0.9.4

View file

@ -145,7 +145,7 @@ ha-ffmpeg==2.0
hangups==0.4.9 hangups==0.4.9
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.14 hass-nabucasa==0.15
# homeassistant.components.mqtt # homeassistant.components.mqtt
hbmqtt==0.9.4 hbmqtt==0.9.4

View file

@ -38,7 +38,7 @@ class MockConfig(config.AbstractConfig):
pass pass
DEFAULT_CONFIG = MockConfig() DEFAULT_CONFIG = MockConfig(None)
def get_new_request(namespace, name, endpoint=None): def get_new_request(namespace, name, endpoint=None):

View file

@ -1012,7 +1012,7 @@ async def test_exclude_filters(hass):
hass.states.async_set( hass.states.async_set(
'cover.deny', 'off', {'friendly_name': "Blocked cover"}) 'cover.deny', 'off', {'friendly_name': "Blocked cover"})
alexa_config = MockConfig() alexa_config = MockConfig(hass)
alexa_config.should_expose = entityfilter.generate_filter( alexa_config.should_expose = entityfilter.generate_filter(
include_domains=[], include_domains=[],
include_entities=[], include_entities=[],
@ -1045,7 +1045,7 @@ async def test_include_filters(hass):
hass.states.async_set( hass.states.async_set(
'group.allow', 'off', {'friendly_name': "Allowed group"}) 'group.allow', 'off', {'friendly_name': "Allowed group"})
alexa_config = MockConfig() alexa_config = MockConfig(hass)
alexa_config.should_expose = entityfilter.generate_filter( alexa_config.should_expose = entityfilter.generate_filter(
include_domains=['automation', 'group'], include_domains=['automation', 'group'],
include_entities=['script.deny'], include_entities=['script.deny'],
@ -1072,7 +1072,7 @@ async def test_never_exposed_entities(hass):
hass.states.async_set( hass.states.async_set(
'group.allow', 'off', {'friendly_name': "Allowed group"}) 'group.allow', 'off', {'friendly_name': "Allowed group"})
alexa_config = MockConfig() alexa_config = MockConfig(hass)
alexa_config.should_expose = entityfilter.generate_filter( alexa_config.should_expose = entityfilter.generate_filter(
include_domains=['group'], include_domains=['group'],
include_entities=[], include_entities=[],
@ -1155,7 +1155,7 @@ async def test_entity_config(hass):
hass.states.async_set( hass.states.async_set(
'light.test_1', 'on', {'friendly_name': "Test light 1"}) 'light.test_1', 'on', {'friendly_name': "Test light 1"})
alexa_config = MockConfig() alexa_config = MockConfig(hass)
alexa_config.entity_config = { alexa_config.entity_config = {
'light.test_1': { 'light.test_1': {
'name': 'Config name', 'name': 'Config name',

View file

@ -3,6 +3,8 @@ import pytest
from unittest.mock import patch from unittest.mock import patch
from homeassistant.components.cloud import prefs
from . import mock_cloud, mock_cloud_prefs from . import mock_cloud, mock_cloud_prefs
@ -18,3 +20,11 @@ def mock_cloud_fixture(hass):
"""Fixture for cloud component.""" """Fixture for cloud component."""
mock_cloud(hass) mock_cloud(hass)
return mock_cloud_prefs(hass) return mock_cloud_prefs(hass)
@pytest.fixture
async def cloud_prefs(hass):
"""Fixture for cloud preferences."""
cloud_prefs = prefs.CloudPreferences(hass)
await cloud_prefs.async_initialize()
return cloud_prefs

View file

@ -8,7 +8,7 @@ import pytest
from homeassistant.core import State from homeassistant.core import State
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components.cloud import ( from homeassistant.components.cloud import (
DOMAIN, ALEXA_SCHEMA, prefs, client) DOMAIN, ALEXA_SCHEMA, client)
from homeassistant.components.cloud.const import ( from homeassistant.components.cloud.const import (
PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE) PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE)
from tests.components.alexa import test_smart_home as test_alexa from tests.components.alexa import test_smart_home as test_alexa
@ -254,18 +254,41 @@ async def test_google_config_should_2fa(
assert not cloud_client.google_config.should_2fa(state) assert not cloud_client.google_config.should_2fa(state)
async def test_alexa_config_expose_entity_prefs(hass): async def test_alexa_config_expose_entity_prefs(hass, cloud_prefs):
"""Test Alexa config should expose using prefs.""" """Test Alexa config should expose using prefs."""
cloud_prefs = prefs.CloudPreferences(hass)
await cloud_prefs.async_initialize()
entity_conf = { entity_conf = {
'should_expose': False 'should_expose': False
} }
await cloud_prefs.async_update(alexa_entity_configs={ await cloud_prefs.async_update(alexa_entity_configs={
'light.kitchen': entity_conf 'light.kitchen': entity_conf
}) })
conf = client.AlexaConfig(ALEXA_SCHEMA({}), cloud_prefs) conf = client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
assert not conf.should_expose('light.kitchen') assert not conf.should_expose('light.kitchen')
entity_conf['should_expose'] = True entity_conf['should_expose'] = True
assert conf.should_expose('light.kitchen') assert conf.should_expose('light.kitchen')
async def test_alexa_config_report_state(hass, cloud_prefs):
"""Test Alexa config should expose using prefs."""
conf = client.AlexaConfig(hass, ALEXA_SCHEMA({}), cloud_prefs, None)
assert cloud_prefs.alexa_report_state is False
assert conf.should_report_state is False
assert conf.is_reporting_states is False
with patch.object(conf, 'async_get_access_token',
return_value=mock_coro("hello")):
await cloud_prefs.async_update(alexa_report_state=True)
await hass.async_block_till_done()
assert cloud_prefs.alexa_report_state is True
assert conf.should_report_state is True
assert conf.is_reporting_states is True
await cloud_prefs.async_update(alexa_report_state=False)
await hass.async_block_till_done()
assert cloud_prefs.alexa_report_state is False
assert conf.should_report_state is False
assert conf.is_reporting_states is False

View file

@ -363,6 +363,7 @@ async def test_websocket_status(hass, hass_ws_client, mock_cloud_fixture,
'google_entity_configs': {}, 'google_entity_configs': {},
'google_secure_devices_pin': None, 'google_secure_devices_pin': None,
'alexa_entity_configs': {}, 'alexa_entity_configs': {},
'alexa_report_state': False,
'remote_enabled': False, 'remote_enabled': False,
}, },
'alexa_entities': { 'alexa_entities': {
@ -371,7 +372,6 @@ async def test_websocket_status(hass, hass_ws_client, mock_cloud_fixture,
'exclude_domains': [], 'exclude_domains': [],
'exclude_entities': [], 'exclude_entities': [],
}, },
'alexa_domains': ['switch'],
'google_entities': { 'google_entities': {
'include_domains': ['light'], 'include_domains': ['light'],
'include_entities': [], 'include_entities': [],