1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Clean up Google Assistant (#11375)

* Clean up Google Assistant

* Fix tests
This commit is contained in:
Paulus Schoutsen
2017-12-31 15:04:49 -08:00
committed by GitHub
parent fcbf7abdaa
commit fc8b25a71f
6 changed files with 165 additions and 183 deletions

View File

@@ -7,53 +7,39 @@ https://home-assistant.io/components/google_assistant/
import asyncio
import logging
from typing import Any, Dict # NOQA
from aiohttp.hdrs import AUTHORIZATION
from aiohttp.web import Request, Response # NOQA
from homeassistant.const import HTTP_UNAUTHORIZED
# Typing imports
# pylint: disable=using-constant-test,unused-import,ungrouped-imports
# if False:
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED
from homeassistant.core import HomeAssistant # NOQA
from homeassistant.core import HomeAssistant, callback # NOQA
from homeassistant.helpers.entity import Entity # NOQA
from .const import (
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
from .smart_home import async_handle_message, Config
_LOGGER = logging.getLogger(__name__)
class GoogleAssistantView(HomeAssistantView):
"""Handle Google Assistant requests."""
@callback
def async_register_http(hass, cfg):
"""Register HTTP views for Google Assistant."""
access_token = cfg.get(CONF_ACCESS_TOKEN)
expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS)
agent_user_id = cfg.get(CONF_AGENT_USER_ID)
url = GOOGLE_ASSISTANT_API_ENDPOINT
name = 'api:google_assistant'
requires_auth = False # Uses access token from oauth flow
def __init__(self, hass: HomeAssistant, cfg: Dict[str, Any]) -> None:
"""Initialize Google Assistant view."""
super().__init__()
self.access_token = cfg.get(CONF_ACCESS_TOKEN)
self.expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT,
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:
def is_exposed(entity) -> bool:
"""Determine if an entity should be exposed to Google Assistant."""
if entity.attributes.get('view') is not None:
# Ignore entities that are views
@@ -63,7 +49,7 @@ class GoogleAssistantView(HomeAssistantView):
explicit_expose = entity.attributes.get(ATTR_GOOGLE_ASSISTANT, None)
domain_exposed_by_default = \
self.expose_by_default and domain in self.exposed_domains
expose_by_default and domain in exposed_domains
# Expose an entity if the entity's domain is exposed by default and
# the configuration doesn't explicitly exclude it from being
@@ -73,79 +59,22 @@ class GoogleAssistantView(HomeAssistantView):
return is_default_exposed or explicit_expose
@asyncio.coroutine
def handle_sync(self, hass: HomeAssistant, request_id: str):
"""Handle SYNC action."""
devices = []
for entity in hass.states.async_all():
if not self.is_entity_exposed(entity):
continue
gass_config = Config(is_exposed, agent_user_id)
hass.http.register_view(
GoogleAssistantView(access_token, gass_config))
device = entity_to_device(entity, hass.config.units)
if device is None:
_LOGGER.warning("No mapping for %s domain", entity.domain)
continue
devices.append(device)
class GoogleAssistantView(HomeAssistantView):
"""Handle Google Assistant requests."""
return self.json(
_make_actions_response(request_id,
{'agentUserId': self.agent_user_id,
'devices': devices}))
url = GOOGLE_ASSISTANT_API_ENDPOINT
name = 'api:google_assistant'
requires_auth = False # Uses access token from oauth flow
@asyncio.coroutine
def handle_query(self,
hass: HomeAssistant,
request_id: str,
requested_devices: list):
"""Handle the QUERY action."""
devices = {}
for device in requested_devices:
devid = device.get('id')
# In theory this should never happpen
if not devid:
_LOGGER.error('Device missing ID: %s', device)
continue
state = hass.states.get(devid)
if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
devices[devid] = query_device(state, hass.config.units)
return self.json(
_make_actions_response(request_id, {'devices': devices}))
@asyncio.coroutine
def handle_execute(self,
hass: HomeAssistant,
request_id: str,
requested_commands: list):
"""Handle the EXECUTE action."""
commands = []
for command in requested_commands:
ent_ids = [ent.get('id') for ent in command.get('devices', [])]
for execution in command.get('execution'):
for eid in ent_ids:
success = False
domain = eid.split('.')[0]
(service, service_data) = determine_service(
eid, execution.get('command'), execution.get('params'),
hass.config.units)
if domain == "group":
domain = "homeassistant"
success = yield from hass.services.async_call(
domain, service, service_data, blocking=True)
result = {"ids": [eid], "states": {}}
if success:
result['status'] = 'SUCCESS'
else:
result['status'] = 'ERROR'
commands.append(result)
return self.json(
_make_actions_response(request_id, {'commands': commands}))
def __init__(self, access_token, gass_config):
"""Initialize the Google Assistant request handler."""
self.access_token = access_token
self.gass_config = gass_config
@asyncio.coroutine
def post(self, request: Request) -> Response:
@@ -155,35 +84,7 @@ class GoogleAssistantView(HomeAssistantView):
return self.json_message(
"missing authorization", status_code=HTTP_UNAUTHORIZED)
data = yield from request.json() # type: dict
inputs = data.get('inputs') # type: list
if len(inputs) != 1:
_LOGGER.error('Too many inputs in request %d', len(inputs))
return self.json_message(
"too many inputs", status_code=HTTP_BAD_REQUEST)
request_id = data.get('requestId') # type: str
intent = inputs[0].get('intent')
payload = inputs[0].get('payload')
hass = request.app['hass'] # type: HomeAssistant
res = None
if intent == 'action.devices.SYNC':
res = yield from self.handle_sync(hass, request_id)
elif intent == 'action.devices.QUERY':
res = yield from self.handle_query(hass, request_id,
payload.get('devices', []))
elif intent == 'action.devices.EXECUTE':
res = yield from self.handle_execute(hass, request_id,
payload.get('commands', []))
if res:
return res
return self.json_message(
"invalid intent", status_code=HTTP_BAD_REQUEST)
def _make_actions_response(request_id: str, payload: dict) -> dict:
return {'requestId': request_id, 'payload': payload}
message = yield from request.json() # type: dict
result = yield from async_handle_message(
request.app['hass'], self.gass_config, message)
return self.json(result)