Google Assistant request sync service (#10165)
* Initial commit for request_sync functionality * Fixes for Tox results * Fixed all tox issues and tested locally with GA * Review comments - api_key, conditional read descriptions * Add test for service
This commit is contained in:
parent
bc23799c71
commit
f6d511ac1a
5 changed files with 106 additions and 7 deletions
|
@ -4,9 +4,13 @@ Support for Actions on Google Assistant Smart Home Control.
|
|||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/google_assistant/
|
||||
"""
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
# Typing imports
|
||||
|
@ -15,11 +19,16 @@ import voluptuous as vol
|
|||
from homeassistant.core import HomeAssistant # NOQA
|
||||
from typing import Dict, Any # NOQA
|
||||
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
from .const import (
|
||||
DOMAIN, CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN,
|
||||
CONF_EXPOSE_BY_DEFAULT, CONF_EXPOSED_DOMAINS
|
||||
CONF_EXPOSE_BY_DEFAULT, CONF_EXPOSED_DOMAINS,
|
||||
CONF_AGENT_USER_ID, CONF_API_KEY,
|
||||
SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL
|
||||
)
|
||||
from .auth import GoogleAssistantAuthView
|
||||
from .http import GoogleAssistantView
|
||||
|
@ -28,6 +37,8 @@ _LOGGER = logging.getLogger(__name__)
|
|||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
DEFAULT_AGENT_USER_ID = 'home-assistant'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: {
|
||||
|
@ -36,17 +47,57 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
vol.Required(CONF_ACCESS_TOKEN): cv.string,
|
||||
vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean,
|
||||
vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list,
|
||||
vol.Optional(CONF_AGENT_USER_ID,
|
||||
default=DEFAULT_AGENT_USER_ID): cv.string,
|
||||
vol.Optional(CONF_API_KEY): cv.string
|
||||
}
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def request_sync(hass):
|
||||
"""Request sync."""
|
||||
hass.services.call(DOMAIN, SERVICE_REQUEST_SYNC)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]):
|
||||
"""Activate Google Actions component."""
|
||||
config = yaml_config.get(DOMAIN, {})
|
||||
|
||||
agent_user_id = config.get(CONF_AGENT_USER_ID)
|
||||
api_key = config.get(CONF_API_KEY)
|
||||
if api_key is not None:
|
||||
descriptions = yield from hass.async_add_job(
|
||||
conf_util.load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml')
|
||||
)
|
||||
hass.http.register_view(GoogleAssistantAuthView(hass, config))
|
||||
hass.http.register_view(GoogleAssistantView(hass, config))
|
||||
|
||||
@asyncio.coroutine
|
||||
def request_sync_service_handler(call):
|
||||
"""Handle request sync service calls."""
|
||||
websession = async_get_clientsession(hass)
|
||||
try:
|
||||
with async_timeout.timeout(5, loop=hass.loop):
|
||||
res = yield from websession.post(
|
||||
REQUEST_SYNC_BASE_URL,
|
||||
params={'key': api_key},
|
||||
json={'agent_user_id': agent_user_id})
|
||||
_LOGGER.info("Submitted request_sync request to Google")
|
||||
res.raise_for_status()
|
||||
except aiohttp.ClientResponseError:
|
||||
body = yield from res.read()
|
||||
_LOGGER.error(
|
||||
'request_sync request failed: %d %s', res.status, body)
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
_LOGGER.error("Could not contact Google for request_sync")
|
||||
|
||||
# Register service only if api key is provided
|
||||
if api_key is not None:
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_REQUEST_SYNC, request_sync_service_handler,
|
||||
descriptions.get(SERVICE_REQUEST_SYNC))
|
||||
|
||||
return True
|
||||
|
|
|
@ -13,6 +13,8 @@ CONF_PROJECT_ID = 'project_id'
|
|||
CONF_ACCESS_TOKEN = 'access_token'
|
||||
CONF_CLIENT_ID = 'client_id'
|
||||
CONF_ALIASES = 'aliases'
|
||||
CONF_AGENT_USER_ID = 'agent_user_id'
|
||||
CONF_API_KEY = 'api_key'
|
||||
|
||||
DEFAULT_EXPOSE_BY_DEFAULT = True
|
||||
DEFAULT_EXPOSED_DOMAINS = [
|
||||
|
@ -44,3 +46,7 @@ TYPE_LIGHT = PREFIX_TYPES + 'LIGHT'
|
|||
TYPE_SWITCH = PREFIX_TYPES + 'SWITCH'
|
||||
TYPE_SCENE = PREFIX_TYPES + 'SCENE'
|
||||
TYPE_THERMOSTAT = PREFIX_TYPES + 'THERMOSTAT'
|
||||
|
||||
SERVICE_REQUEST_SYNC = 'request_sync'
|
||||
HOMEGRAPH_URL = 'https://homegraph.googleapis.com/'
|
||||
REQUEST_SYNC_BASE_URL = HOMEGRAPH_URL + 'v1/devices:requestSync'
|
||||
|
|
|
@ -21,10 +21,16 @@ from homeassistant.core import HomeAssistant # NOQA
|
|||
from homeassistant.helpers.entity import Entity # NOQA
|
||||
|
||||
from .const import (
|
||||
CONF_ACCESS_TOKEN, CONF_EXPOSED_DOMAINS, ATTR_GOOGLE_ASSISTANT,
|
||||
CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSED_DOMAINS, DEFAULT_EXPOSE_BY_DEFAULT,
|
||||
GOOGLE_ASSISTANT_API_ENDPOINT)
|
||||
from .smart_home import query_device, entity_to_device, determine_service
|
||||
GOOGLE_ASSISTANT_API_ENDPOINT,
|
||||
CONF_ACCESS_TOKEN,
|
||||
DEFAULT_EXPOSE_BY_DEFAULT,
|
||||
DEFAULT_EXPOSED_DOMAINS,
|
||||
CONF_EXPOSE_BY_DEFAULT,
|
||||
CONF_EXPOSED_DOMAINS,
|
||||
ATTR_GOOGLE_ASSISTANT,
|
||||
CONF_AGENT_USER_ID
|
||||
)
|
||||
from .smart_home import entity_to_device, query_device, determine_service
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -45,6 +51,7 @@ class GoogleAssistantView(HomeAssistantView):
|
|||
DEFAULT_EXPOSE_BY_DEFAULT)
|
||||
self.exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS,
|
||||
DEFAULT_EXPOSED_DOMAINS)
|
||||
self.agent_user_id = cfg.get(CONF_AGENT_USER_ID)
|
||||
|
||||
def is_entity_exposed(self, entity) -> bool:
|
||||
"""Determine if an entity should be exposed to Google Assistant."""
|
||||
|
@ -82,7 +89,9 @@ class GoogleAssistantView(HomeAssistantView):
|
|||
devices.append(device)
|
||||
|
||||
return self.json(
|
||||
make_actions_response(request_id, {'devices': devices}))
|
||||
make_actions_response(request_id,
|
||||
{'agentUserId': self.agent_user_id,
|
||||
'devices': devices}))
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_query(self,
|
||||
|
|
2
homeassistant/components/google_assistant/services.yaml
Normal file
2
homeassistant/components/google_assistant/services.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
request_sync:
|
||||
description: Send a request_sync command to Google.
|
31
tests/components/google_assistant/test_init.py
Normal file
31
tests/components/google_assistant/test_init.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""The tests for google-assistant init."""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components import google_assistant as ga
|
||||
|
||||
GA_API_KEY = "Agdgjsj399sdfkosd932ksd"
|
||||
GA_AGENT_USER_ID = "testid"
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_request_sync_service(aioclient_mock, hass):
|
||||
"""Test that it posts to the request_sync url."""
|
||||
aioclient_mock.post(
|
||||
ga.const.REQUEST_SYNC_BASE_URL, status=200)
|
||||
|
||||
yield from async_setup_component(hass, 'google_assistant', {
|
||||
'google_assistant': {
|
||||
'project_id': 'test_project',
|
||||
'client_id': 'r7328kwdsdfsdf03223409',
|
||||
'access_token': '8wdsfjsf932492342349234',
|
||||
'agent_user_id': GA_AGENT_USER_ID,
|
||||
'api_key': GA_API_KEY
|
||||
}})
|
||||
|
||||
assert aioclient_mock.call_count == 0
|
||||
yield from hass.services.async_call(ga.const.DOMAIN,
|
||||
ga.const.SERVICE_REQUEST_SYNC,
|
||||
blocking=True)
|
||||
|
||||
assert aioclient_mock.call_count == 1
|
Loading…
Add table
Add a link
Reference in a new issue