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:
r4nd0mbr1ck 2017-11-14 03:32:23 +11:00 committed by Paulus Schoutsen
parent bc23799c71
commit f6d511ac1a
5 changed files with 106 additions and 7 deletions

View file

@ -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

View file

@ -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'

View file

@ -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,

View file

@ -0,0 +1,2 @@
request_sync:
description: Send a request_sync command to Google.

View 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