Skip to content

Instantly share code, notes, and snippets.

@jamesbulpin
Created December 17, 2017 12:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jamesbulpin/a12f06cabc9a15d4b5263e9e313313aa to your computer and use it in GitHub Desktop.
Save jamesbulpin/a12f06cabc9a15d4b5263e9e313313aa to your computer and use it in GitHub Desktop.
Azure Function code for a demo Alexa Skill handler for the Christmas Jumper project
// Alexa skill handler for the "Citrix" demo skill.
//
// This runs as an Azure Function and must have the NPM libraries installed into the
// Function App image: ensure that package.json is populated with the required
// dependencies and follow the instructions at
// https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node#node-version-and-package-management
//
// Example package.json:
// {
// "dependencies": {
// "azure-iothub": "^1.1.17",
// "azure-iot-common": "^1.2.0"
// }
// }
//
// Secrets are stored as Application Settings and accessed here via environment variables.
// They are:
// CFG_IOTHUB_CONNECTION_STRING - connection string for the Azure IoT Hub
// CFG_ALEXA_SKILL_ID_CITRIX - Alexa Skill App ID
//
// MIT License
//
// Copyright (c) [2017] [Citrix Systems, Inc]
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
var Client = require('azure-iothub').Client;
var Message = require('azure-iot-common').Message;
var querystring = require('querystring');
// The deviceName of the xmas jumper demo
var deviceNameXmas = 'xmas';
// Functions to help build Alexa responses
function addToResponseSpeech(response, text) {
response.response.outputSpeech = {
'type': 'PlainText',
'text': text
};
}
// These are templates for the side-effects the skill may have. Each returns an object
// that will go on the queue for processing.
function sideEffectIoT(deviceName, message) {
return {
fn: handleCallIoT,
args: {
deviceName: deviceName,
message: message
}
}
}
// Side-effect handlers. Each one will do its thing and then call a common callback.
// The callback will either call the next handler, or if no more are left, complete
// the original Alexa request.
function sideEffectHandlerCallback(context, sideEffectList, err, result) {
if (err) {
// Do not call any further handlers. If the previous handler did not
// set an error code on the response then do this now.
if (!context.res || !context.res.status) {
context.res = {
status: 500,
body: "Handler error: " + err
};
}
context.done();
return;
}
var next = sideEffectList.pop();
if (!next) {
// No more handlers, complete the request
context.done();
return;
}
// Call the next handler
next.fn(context, sideEffectHandlerCallback.bind(null, context, sideEffectList), next.args);
}
function callSideEffectHandlers(context, sideEffectList) {
sideEffectHandlerCallback(context, sideEffectList, null, null);
}
function handleCallIoT(context, callback, args) {
if (!process.env.CFG_IOTHUB_CONNECTION_STRING) {
context.log('Application setting "CFG_IOTHUB_CONNECTION_STRING" missing.');
context.res = {
status: 500,
body: "IoT Hub not configured."
};
callback("IoT Hub not configured", null);
return;
}
var serviceClient = Client.fromConnectionString(process.env.CFG_IOTHUB_CONNECTION_STRING);
serviceClient.open(function (err) {
if (err) {
context.log('Could not connect: ' + err.message);
context.res = {
status: 500,
body: "Could not connect to Iot Hub: " + err.message
};
context.done();
} else {
context.log('Service client connected');
var msg = new Message(args.message);
msg.ack = 'full';
msg.messageId = "My Message ID";
context.log('Sending message: ' + msg.getData());
serviceClient.send(args.deviceName, msg, callback);
}
});
}
// Main Alexa skill handler
module.exports = function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.', JSON.stringify(req, null, 2));
if (!process.env.CFG_ALEXA_SKILL_ID_CITRIX) {
context.log('Application setting "CFG_ALEXA_SKILL_ID_CITRIX" missing.');
context.res = {
status: 500,
body: "Alexa skill ID config missing"
};
context.done();
return;
}
// Check that we're being called by the Alexa infrastructure
context.log('Application ID', req.body.session.application.applicationId);
if (req.body.session.application.applicationId != process.env.CFG_ALEXA_SKILL_ID_CITRIX) {
context.res = {
status: 403,
body: "Unauthorized application ID"
};
context.done();
return;
}
var response = {
'version': '1.0',
'sessionAttributes': {},
'response': {
'shouldEndSession': true
}
};
// Build a list of side-effect actions to take
sideEffects = [];
if (req.body.request.type == "IntentRequest") {
var intent = req.body.request.intent;
switch (intent.name) {
case "XmasIntent":
var color = req.body.request.intent.slots.color.value;
sideEffects.push(sideEffectIoT(deviceNameXmas, color));
addToResponseSpeech(response, "OK");
break;
}
}
context.res = {
// status: 200, /* Defaults to 200 */
body: response
};
callSideEffectHandlers(context, sideEffects);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment