Clean up Alexa.intent and DialogFlow.intent (#11492)

* Clean up Alexa.intent

* Restructure dialogflow too

* Lint

* Lint
pull/11437/merge
Paulus Schoutsen 2018-01-06 16:39:32 -08:00 committed by Pascal Vizeli
parent c613585100
commit 5c2cecde70
3 changed files with 169 additions and 103 deletions

View File

@ -9,15 +9,16 @@ import asyncio
import enum import enum
import logging import logging
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import intent from homeassistant.helpers import intent
from homeassistant.components import http from homeassistant.components import http
from homeassistant.util.decorator import Registry
from .const import DOMAIN, SYN_RESOLUTION_MATCH from .const import DOMAIN, SYN_RESOLUTION_MATCH
INTENTS_API_ENDPOINT = '/api/alexa' INTENTS_API_ENDPOINT = '/api/alexa'
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -47,6 +48,10 @@ def async_setup(hass):
hass.http.register_view(AlexaIntentsView) hass.http.register_view(AlexaIntentsView)
class UnknownRequest(HomeAssistantError):
"""When an unknown Alexa request is passed in."""
class AlexaIntentsView(http.HomeAssistantView): class AlexaIntentsView(http.HomeAssistantView):
"""Handle Alexa requests.""" """Handle Alexa requests."""
@ -57,57 +62,98 @@ class AlexaIntentsView(http.HomeAssistantView):
def post(self, request): def post(self, request):
"""Handle Alexa.""" """Handle Alexa."""
hass = request.app['hass'] hass = request.app['hass']
data = yield from request.json() message = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', data) _LOGGER.debug('Received Alexa request: %s', message)
req = data.get('request') try:
response = yield from async_handle_message(hass, message)
return b'' if response is None else self.json(response)
except UnknownRequest as err:
_LOGGER.warning(str(err))
return self.json(intent_error_response(
hass, message, str(err)))
if req is None: except intent.UnknownIntent as err:
_LOGGER.error('Received invalid data from Alexa: %s', data) _LOGGER.warning(str(err))
return self.json_message('Expected request value not received', return self.json(intent_error_response(
HTTP_BAD_REQUEST) hass, message,
"This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
return self.json(intent_error_response(
hass, message,
"Invalid slot information received for this intent."))
except intent.IntentError as err:
_LOGGER.exception(str(err))
return self.json(intent_error_response(
hass, message, "Error handling intent."))
def intent_error_response(hass, message, error):
"""Return an Alexa response that will speak the error message."""
alexa_intent_info = message.get('request').get('intent')
alexa_response = AlexaResponse(hass, alexa_intent_info)
alexa_response.add_speech(SpeechType.plaintext, error)
return alexa_response.as_dict()
@asyncio.coroutine
def async_handle_message(hass, message):
"""Handle an Alexa intent.
Raises:
- UnknownRequest
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
req_type = req['type'] req_type = req['type']
if req_type == 'SessionEndedRequest': handler = HANDLERS.get(req_type)
if not handler:
raise UnknownRequest('Received unknown request {}'.format(req_type))
return (yield from handler(hass, message))
@HANDLERS.register('SessionEndedRequest')
@asyncio.coroutine
def async_handle_session_end(hass, message):
"""Handle a session end request."""
return None return None
@HANDLERS.register('IntentRequest')
@HANDLERS.register('LaunchRequest')
@asyncio.coroutine
def async_handle_intent(hass, message):
"""Handle an intent request.
Raises:
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
alexa_intent_info = req.get('intent') alexa_intent_info = req.get('intent')
alexa_response = AlexaResponse(hass, alexa_intent_info) alexa_response = AlexaResponse(hass, alexa_intent_info)
if req_type != 'IntentRequest' and req_type != 'LaunchRequest': if req['type'] == 'LaunchRequest':
_LOGGER.warning('Received unsupported request: %s', req_type) intent_name = message.get('session', {}) \
return self.json_message(
'Received unsupported request: {}'.format(req_type),
HTTP_BAD_REQUEST)
if req_type == 'LaunchRequest':
intent_name = data.get('session', {}) \
.get('application', {}) \ .get('application', {}) \
.get('applicationId') .get('applicationId')
else: else:
intent_name = alexa_intent_info['name'] intent_name = alexa_intent_info['name']
try:
intent_response = yield from intent.async_handle( intent_response = yield from intent.async_handle(
hass, DOMAIN, intent_name, hass, DOMAIN, intent_name,
{key: {'value': value} for key, value {key: {'value': value} for key, value
in alexa_response.variables.items()}) in alexa_response.variables.items()})
except intent.UnknownIntent as err:
_LOGGER.warning('Received unknown intent %s', intent_name)
alexa_response.add_speech(
SpeechType.plaintext,
"This intent is not yet configured within Home Assistant.")
return self.json(alexa_response)
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception('Error handling request for %s', intent_name)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items(): for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
if intent_speech in intent_response.speech: if intent_speech in intent_response.speech:
@ -121,7 +167,7 @@ class AlexaIntentsView(http.HomeAssistantView):
CardType.simple, intent_response.card['simple']['title'], CardType.simple, intent_response.card['simple']['title'],
intent_response.card['simple']['content']) intent_response.card['simple']['content'])
return self.json(alexa_response) return alexa_response.as_dict()
def resolve_slot_synonyms(key, request): def resolve_slot_synonyms(key, request):

View File

@ -9,7 +9,7 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, template from homeassistant.helpers import intent, template
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
@ -33,6 +33,10 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
class DialogFlowError(HomeAssistantError):
"""Raised when a DialogFlow error happens."""
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
"""Set up Dialogflow component.""" """Set up Dialogflow component."""
@ -51,57 +55,71 @@ class DialogflowIntentsView(HomeAssistantView):
def post(self, request): def post(self, request):
"""Handle Dialogflow.""" """Handle Dialogflow."""
hass = request.app['hass'] hass = request.app['hass']
data = yield from request.json() message = yield from request.json()
_LOGGER.debug("Received Dialogflow request: %s", data) _LOGGER.debug("Received Dialogflow request: %s", message)
req = data.get('result') try:
response = yield from async_handle_message(hass, message)
return b'' if response is None else self.json(response)
if req is None: except DialogFlowError as err:
_LOGGER.error("Received invalid data from Dialogflow: %s", data) _LOGGER.warning(str(err))
return self.json_message( return self.json(dialogflow_error_response(
"Expected result value not received", HTTP_BAD_REQUEST) hass, message, str(err)))
except intent.UnknownIntent as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"Invalid slot information received for this intent."))
except intent.IntentError as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message, "Error handling intent."))
def dialogflow_error_response(hass, message, error):
"""Return a response saying the error message."""
dialogflow_response = DialogflowResponse(message['result']['parameters'])
dialogflow_response.add_speech(error)
return dialogflow_response.as_dict()
@asyncio.coroutine
def async_handle_message(hass, message):
"""Handle a DialogFlow message."""
req = message.get('result')
action_incomplete = req['actionIncomplete'] action_incomplete = req['actionIncomplete']
if action_incomplete: if action_incomplete:
return None return None
action = req.get('action') action = req.get('action', '')
parameters = req.get('parameters') parameters = req.get('parameters')
dialogflow_response = DialogflowResponse(parameters) dialogflow_response = DialogflowResponse(parameters)
if action == "": if action == "":
_LOGGER.warning("Received intent with empty action") raise DialogFlowError(
dialogflow_response.add_speech(
"You have not defined an action in your Dialogflow intent.") "You have not defined an action in your Dialogflow intent.")
return self.json(dialogflow_response)
try:
intent_response = yield from intent.async_handle( intent_response = yield from intent.async_handle(
hass, DOMAIN, action, hass, DOMAIN, action,
{key: {'value': value} for key, value {key: {'value': value} for key, value
in parameters.items()}) in parameters.items()})
except intent.UnknownIntent as err:
_LOGGER.warning("Received unknown intent %s", action)
dialogflow_response.add_speech(
"This intent is not yet configured within Home Assistant.")
return self.json(dialogflow_response)
except intent.InvalidSlotInfo as err:
_LOGGER.error("Received invalid slot data: %s", err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception("Error handling request for %s", action)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
if 'plain' in intent_response.speech: if 'plain' in intent_response.speech:
dialogflow_response.add_speech( dialogflow_response.add_speech(
intent_response.speech['plain']['speech']) intent_response.speech['plain']['speech'])
return self.json(dialogflow_response) return dialogflow_response.as_dict()
class DialogflowResponse(object): class DialogflowResponse(object):

View File

@ -41,7 +41,7 @@ def async_handle(hass, platform, intent_type, slots=None, text_input=None):
handler = hass.data.get(DATA_KEY, {}).get(intent_type) handler = hass.data.get(DATA_KEY, {}).get(intent_type)
if handler is None: if handler is None:
raise UnknownIntent() raise UnknownIntent('Unknown intent {}'.format(intent_type))
intent = Intent(hass, platform, intent_type, slots or {}, text_input) intent = Intent(hass, platform, intent_type, slots or {}, text_input)
@ -50,9 +50,11 @@ def async_handle(hass, platform, intent_type, slots=None, text_input=None):
result = yield from handler.async_handle(intent) result = yield from handler.async_handle(intent)
return result return result
except vol.Invalid as err: except vol.Invalid as err:
raise InvalidSlotInfo from err raise InvalidSlotInfo(
'Received invalid slot info for {}'.format(intent_type)) from err
except Exception as err: except Exception as err:
raise IntentHandleError from err raise IntentHandleError(
'Error handling {}'.format(intent_type)) from err
class IntentError(HomeAssistantError): class IntentError(HomeAssistantError):