173 lines
5.1 KiB
Python
173 lines
5.1 KiB
Python
"""
|
|
Support for API.AI webhook.
|
|
|
|
For more details about this component, please refer to the documentation at
|
|
https://home-assistant.io/components/apiai/
|
|
"""
|
|
import asyncio
|
|
import copy
|
|
import logging
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import PROJECT_NAME, HTTP_BAD_REQUEST
|
|
from homeassistant.helpers import template, script, config_validation as cv
|
|
from homeassistant.components.http import HomeAssistantView
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
INTENTS_API_ENDPOINT = '/api/apiai'
|
|
|
|
CONF_INTENTS = 'intents'
|
|
CONF_SPEECH = 'speech'
|
|
CONF_ACTION = 'action'
|
|
CONF_ASYNC_ACTION = 'async_action'
|
|
|
|
DEFAULT_CONF_ASYNC_ACTION = False
|
|
|
|
DOMAIN = 'apiai'
|
|
DEPENDENCIES = ['http']
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: {
|
|
CONF_INTENTS: {
|
|
cv.string: {
|
|
vol.Optional(CONF_SPEECH): cv.template,
|
|
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
|
vol.Optional(CONF_ASYNC_ACTION,
|
|
default=DEFAULT_CONF_ASYNC_ACTION): cv.boolean
|
|
}
|
|
}
|
|
}
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
def setup(hass, config):
|
|
"""Activate API.AI component."""
|
|
intents = config[DOMAIN].get(CONF_INTENTS, {})
|
|
|
|
hass.http.register_view(ApiaiIntentsView(hass, intents))
|
|
|
|
return True
|
|
|
|
|
|
class ApiaiIntentsView(HomeAssistantView):
|
|
"""Handle API.AI requests."""
|
|
|
|
url = INTENTS_API_ENDPOINT
|
|
name = 'api:apiai'
|
|
|
|
def __init__(self, hass, intents):
|
|
"""Initialize API.AI view."""
|
|
super().__init__()
|
|
|
|
self.hass = hass
|
|
intents = copy.deepcopy(intents)
|
|
template.attach(hass, intents)
|
|
|
|
for name, intent in intents.items():
|
|
if CONF_ACTION in intent:
|
|
intent[CONF_ACTION] = script.Script(
|
|
hass, intent[CONF_ACTION], "Apiai intent {}".format(name))
|
|
|
|
self.intents = intents
|
|
|
|
@asyncio.coroutine
|
|
def post(self, request):
|
|
"""Handle API.AI."""
|
|
data = yield from request.json()
|
|
|
|
_LOGGER.debug("Received api.ai request: %s", data)
|
|
|
|
req = data.get('result')
|
|
|
|
if req is None:
|
|
_LOGGER.error("Received invalid data from api.ai: %s", data)
|
|
return self.json_message(
|
|
"Expected result value not received", HTTP_BAD_REQUEST)
|
|
|
|
action_incomplete = req['actionIncomplete']
|
|
|
|
if action_incomplete:
|
|
return None
|
|
|
|
# use intent to no mix HASS actions with this parameter
|
|
intent = req.get('action')
|
|
parameters = req.get('parameters')
|
|
# contexts = req.get('contexts')
|
|
response = ApiaiResponse(parameters)
|
|
|
|
# Default Welcome Intent
|
|
# Maybe is better to handle this in api.ai directly?
|
|
#
|
|
# if intent == 'input.welcome':
|
|
# response.add_speech(
|
|
# "Hello, and welcome to the future. How may I help?")
|
|
# return self.json(response)
|
|
|
|
if intent == "":
|
|
_LOGGER.warning("Received intent with empty action")
|
|
response.add_speech(
|
|
"You have not defined an action in your api.ai intent.")
|
|
return self.json(response)
|
|
|
|
config = self.intents.get(intent)
|
|
|
|
if config is None:
|
|
_LOGGER.warning("Received unknown intent %s", intent)
|
|
response.add_speech(
|
|
"Intent '%s' is not yet configured within Home Assistant." %
|
|
intent)
|
|
return self.json(response)
|
|
|
|
speech = config.get(CONF_SPEECH)
|
|
action = config.get(CONF_ACTION)
|
|
async_action = config.get(CONF_ASYNC_ACTION)
|
|
|
|
if action is not None:
|
|
# API.AI expects a response in less than 5s
|
|
if async_action:
|
|
# Do not wait for the action to be executed.
|
|
# Needed if the action will take longer than 5s to execute
|
|
self.hass.async_add_job(action.async_run(response.parameters))
|
|
else:
|
|
# Wait for the action to be executed so we can use results to
|
|
# render the answer
|
|
yield from action.async_run(response.parameters)
|
|
|
|
# pylint: disable=unsubscriptable-object
|
|
if speech is not None:
|
|
response.add_speech(speech)
|
|
|
|
return self.json(response)
|
|
|
|
|
|
class ApiaiResponse(object):
|
|
"""Help generating the response for API.AI."""
|
|
|
|
def __init__(self, parameters):
|
|
"""Initialize the response."""
|
|
self.speech = None
|
|
self.parameters = {}
|
|
# Parameter names replace '.' and '-' for '_'
|
|
for key, value in parameters.items():
|
|
underscored_key = key.replace('.', '_').replace('-', '_')
|
|
self.parameters[underscored_key] = value
|
|
|
|
def add_speech(self, text):
|
|
"""Add speech to the response."""
|
|
assert self.speech is None
|
|
|
|
if isinstance(text, template.Template):
|
|
text = text.async_render(self.parameters)
|
|
|
|
self.speech = text
|
|
|
|
def as_dict(self):
|
|
"""Return response in an API.AI valid dict."""
|
|
return {
|
|
'speech': self.speech,
|
|
'displayText': self.speech,
|
|
'source': PROJECT_NAME,
|
|
}
|