Clean up Alexa.intent and DialogFlow.intent (#11492)
* Clean up Alexa.intent * Restructure dialogflow too * Lint * Lintpull/11437/merge
parent
c613585100
commit
5c2cecde70
|
@ -9,15 +9,16 @@ import asyncio
|
|||
import enum
|
||||
import logging
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import HTTP_BAD_REQUEST
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.components import http
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
from .const import DOMAIN, SYN_RESOLUTION_MATCH
|
||||
|
||||
INTENTS_API_ENDPOINT = '/api/alexa'
|
||||
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -47,6 +48,10 @@ def async_setup(hass):
|
|||
hass.http.register_view(AlexaIntentsView)
|
||||
|
||||
|
||||
class UnknownRequest(HomeAssistantError):
|
||||
"""When an unknown Alexa request is passed in."""
|
||||
|
||||
|
||||
class AlexaIntentsView(http.HomeAssistantView):
|
||||
"""Handle Alexa requests."""
|
||||
|
||||
|
@ -57,71 +62,112 @@ class AlexaIntentsView(http.HomeAssistantView):
|
|||
def post(self, request):
|
||||
"""Handle Alexa."""
|
||||
hass = request.app['hass']
|
||||
data = yield from request.json()
|
||||
message = yield from request.json()
|
||||
|
||||
_LOGGER.debug('Received Alexa request: %s', data)
|
||||
|
||||
req = data.get('request')
|
||||
|
||||
if req is None:
|
||||
_LOGGER.error('Received invalid data from Alexa: %s', data)
|
||||
return self.json_message('Expected request value not received',
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
req_type = req['type']
|
||||
|
||||
if req_type == 'SessionEndedRequest':
|
||||
return None
|
||||
|
||||
alexa_intent_info = req.get('intent')
|
||||
alexa_response = AlexaResponse(hass, alexa_intent_info)
|
||||
|
||||
if req_type != 'IntentRequest' and req_type != 'LaunchRequest':
|
||||
_LOGGER.warning('Received unsupported request: %s', req_type)
|
||||
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('applicationId')
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
_LOGGER.debug('Received Alexa request: %s', message)
|
||||
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, intent_name,
|
||||
{key: {'value': value} for key, value
|
||||
in alexa_response.variables.items()})
|
||||
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)))
|
||||
|
||||
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)
|
||||
_LOGGER.warning(str(err))
|
||||
return self.json(intent_error_response(
|
||||
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_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)
|
||||
return self.json(intent_error_response(
|
||||
hass, message,
|
||||
"Invalid slot information received for this intent."))
|
||||
|
||||
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
|
||||
if intent_speech in intent_response.speech:
|
||||
alexa_response.add_speech(
|
||||
alexa_speech,
|
||||
intent_response.speech[intent_speech]['speech'])
|
||||
break
|
||||
except intent.IntentError as err:
|
||||
_LOGGER.exception(str(err))
|
||||
return self.json(intent_error_response(
|
||||
hass, message, "Error handling intent."))
|
||||
|
||||
if 'simple' in intent_response.card:
|
||||
alexa_response.add_card(
|
||||
CardType.simple, intent_response.card['simple']['title'],
|
||||
intent_response.card['simple']['content'])
|
||||
|
||||
return self.json(alexa_response)
|
||||
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']
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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_response = AlexaResponse(hass, alexa_intent_info)
|
||||
|
||||
if req['type'] == 'LaunchRequest':
|
||||
intent_name = message.get('session', {}) \
|
||||
.get('application', {}) \
|
||||
.get('applicationId')
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, intent_name,
|
||||
{key: {'value': value} for key, value
|
||||
in alexa_response.variables.items()})
|
||||
|
||||
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
|
||||
if intent_speech in intent_response.speech:
|
||||
alexa_response.add_speech(
|
||||
alexa_speech,
|
||||
intent_response.speech[intent_speech]['speech'])
|
||||
break
|
||||
|
||||
if 'simple' in intent_response.card:
|
||||
alexa_response.add_card(
|
||||
CardType.simple, intent_response.card['simple']['title'],
|
||||
intent_response.card['simple']['content'])
|
||||
|
||||
return alexa_response.as_dict()
|
||||
|
||||
|
||||
def resolve_slot_synonyms(key, request):
|
||||
|
|
|
@ -9,7 +9,7 @@ import logging
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import HTTP_BAD_REQUEST
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import intent, template
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
|
||||
|
@ -33,6 +33,10 @@ CONFIG_SCHEMA = vol.Schema({
|
|||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
class DialogFlowError(HomeAssistantError):
|
||||
"""Raised when a DialogFlow error happens."""
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up Dialogflow component."""
|
||||
|
@ -51,57 +55,71 @@ class DialogflowIntentsView(HomeAssistantView):
|
|||
def post(self, request):
|
||||
"""Handle Dialogflow."""
|
||||
hass = request.app['hass']
|
||||
data = yield from request.json()
|
||||
message = yield from request.json()
|
||||
|
||||
_LOGGER.debug("Received Dialogflow request: %s", data)
|
||||
|
||||
req = data.get('result')
|
||||
|
||||
if req is None:
|
||||
_LOGGER.error("Received invalid data from Dialogflow: %s", data)
|
||||
return self.json_message(
|
||||
"Expected result value not received", HTTP_BAD_REQUEST)
|
||||
|
||||
action_incomplete = req['actionIncomplete']
|
||||
|
||||
if action_incomplete:
|
||||
return None
|
||||
|
||||
action = req.get('action')
|
||||
parameters = req.get('parameters')
|
||||
dialogflow_response = DialogflowResponse(parameters)
|
||||
|
||||
if action == "":
|
||||
_LOGGER.warning("Received intent with empty action")
|
||||
dialogflow_response.add_speech(
|
||||
"You have not defined an action in your Dialogflow intent.")
|
||||
return self.json(dialogflow_response)
|
||||
_LOGGER.debug("Received Dialogflow request: %s", message)
|
||||
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, action,
|
||||
{key: {'value': value} for key, value
|
||||
in parameters.items()})
|
||||
response = yield from async_handle_message(hass, message)
|
||||
return b'' if response is None else self.json(response)
|
||||
|
||||
except DialogFlowError as err:
|
||||
_LOGGER.warning(str(err))
|
||||
return self.json(dialogflow_error_response(
|
||||
hass, message, str(err)))
|
||||
|
||||
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)
|
||||
_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.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)
|
||||
_LOGGER.warning(str(err))
|
||||
return self.json(dialogflow_error_response(
|
||||
hass, message,
|
||||
"Invalid slot information received for this intent."))
|
||||
|
||||
if 'plain' in intent_response.speech:
|
||||
dialogflow_response.add_speech(
|
||||
intent_response.speech['plain']['speech'])
|
||||
except intent.IntentError as err:
|
||||
_LOGGER.warning(str(err))
|
||||
return self.json(dialogflow_error_response(
|
||||
hass, message, "Error handling intent."))
|
||||
|
||||
return self.json(dialogflow_response)
|
||||
|
||||
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']
|
||||
|
||||
if action_incomplete:
|
||||
return None
|
||||
|
||||
action = req.get('action', '')
|
||||
parameters = req.get('parameters')
|
||||
dialogflow_response = DialogflowResponse(parameters)
|
||||
|
||||
if action == "":
|
||||
raise DialogFlowError(
|
||||
"You have not defined an action in your Dialogflow intent.")
|
||||
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, action,
|
||||
{key: {'value': value} for key, value
|
||||
in parameters.items()})
|
||||
|
||||
if 'plain' in intent_response.speech:
|
||||
dialogflow_response.add_speech(
|
||||
intent_response.speech['plain']['speech'])
|
||||
|
||||
return dialogflow_response.as_dict()
|
||||
|
||||
|
||||
class DialogflowResponse(object):
|
||||
|
|
|
@ -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)
|
||||
|
||||
if handler is None:
|
||||
raise UnknownIntent()
|
||||
raise UnknownIntent('Unknown intent {}'.format(intent_type))
|
||||
|
||||
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)
|
||||
return result
|
||||
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:
|
||||
raise IntentHandleError from err
|
||||
raise IntentHandleError(
|
||||
'Error handling {}'.format(intent_type)) from err
|
||||
|
||||
|
||||
class IntentError(HomeAssistantError):
|
||||
|
|
Loading…
Reference in New Issue