Voice-enabling smart-home devices with Amazon Alexa (Part II)

In the previous post, we discussed setting-up Amazon Alexa skills and an environment for building and deploying an autoscaling service to host processing of the skill intents.
Let’s start coding our AWS Lambda functions to handle events sent to us by Alexa skills.
In our lambda_function_smart_home.js, we handle events sent to us by the smarthome skill. The code is below.

'use strict';

import HandlerProvider from './alexa/smarthome/handler_provider.js';
import * as utils from './alexa/common/utils.js';
import logging from './alexa/common/logging.js';
import * as exception from './alexa/common/exception.js';

var handle_event_async = async function(event, context, callback)
{
   // For more details on the format of the requests served by this lambda function check here:
   // https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#request-format
   logging.info(event);
   try {
       // Prevent someone else from configuring a skill that sends requests to this function
       var session = event.session;
       utils.validate_application_id(session.application.applicationId);
       var provider = new HandlerProvider(event);
       var handler = provider.get_handler();
       var res = await handler.handle_event(event);
       console.log(JSON.stringify(res));
       callback(null, res);
   } catch (error) {
       console.log(error);
       if(error instanceof exception.SmartHomeException) {
           logging.error(error.get_error_response());
           callback(null, error.get_error_response());
       } else {
           logging.error("Unable to return a sensible response to the user due to error: 1");
           var errorUnhandled = new exception.DriverInternalError();
           callback(null, errorUnhandled.get_error_response());
       }
   }   
}

export function lambda_handler(event, context, callback) {
   handle_event_async(event, context, callback);   
}

In our function, actual intents are handled by the Smart Home handler class.

Let’s init our npm project, if you have no done it yet, and add packages and scripts necessary to compile our code.
First, we put together .babelrc for transpilation, below:

{
 "presets": [
   ["latest", { "modules": false }]
 ],
 "plugins": ["babel-plugin-add-module-exports"]
}

We will need the babel exports plugin for our tests, once the skill is ready. Now, we got the add some packages to our node project. We used the below version:
"devDependencies": {
   "babel-cli": "^6.18.0",
   "babel-core": "^6.21.0",
   "babel-eslint": "^7.1.1",
   "babel-loader": "^6.2.10",
   "babel-plugin-add-module-exports": "^0.2.1",
   "babel-polyfill": "^6.20.0",
   "babel-preset-latest": "^6.16.0",
   "babel-runtime": "^6.20.0",
   "webpack": "2.2.1"
}

Now, the packages are in place. Let’s add scripts to build, test and deploy our project. We added the following commands to our package.json file.
scripts": {
   "test": "./node_modules/mocha/bin/mocha --require babel-polyfill --no-timeouts --colors",
   "clean": "rimraf lib dist coverage",
   "build:smarthome:us:dev": "./node_modules/.bin/webpack --env.skill=smarthome --env.locale=us --env.stage=dev --config webpack/webpack.config.dev.js",
   …
   "build:smarthome:all": "npm run build:smarthome:us:all && npm run build:smarthome:eu:all",
   "build:customskill:all": "npm run build:customskill:us:all && npm run build:customskill:eu:all",
   "build": "npm run build:smarthome:all && npm run build:customskill:all",
   "build:debug": "./node_modules/.bin/webpack src/app.js dist/debug/app.js --config webpack/debug/webpack.config.debug.js",
   "deploy": "sh ./deploy.sh"
}

The last script deals with serverless. We found it easier, in our case, to have a separate shell script to invoke the serverless framework executable. This allowed us to both: use cached credentials for deployment from CI tools, and to do an interactive deployment when invoked manually, with some of the parameters omitted.

Now, our code will compile and produce a single JavaScript file suitable for AWS Lambda deployment. To work locally, without having to deploy to Amazon to try every code modification, we used the app.js file that looks like the below:

'use strict';

//import {lambda_handler} from './lambda_function_custom_skill';
import {lambda_handler} from './lambda_function_smart_home';

let applianceId = "e96b94ba-da2b-.................";
let createAmazonHelpEvent = function(access_token)
{
   return {
     "session": {
       "sessionId": "SessionId.db5adc55-878f-43ac-a58d-..........",
       "application": {
         "applicationId": "amzn1.ask.skill.f27c2889-d3f5-....-....-........."
       },
       "attributes": {},
       "user": {
         "userId": "amzn1.ask.account.A……………………………………………………………………………………….",
         "accessToken": access_token
       },
       "new": true
     },
     "request": {
       "type": "IntentRequest",
       "requestId": "EdwRequestId.b9caccff-34c7-4bcb-....-............",
       "locale": "en-US",
       "timestamp": "2016-11-03T19:56:51Z",
       "intent": {
         "name": "AMAZON.HelpIntent",
         "slots": {}
       }
     },
     "version": "1.0"
   };
};

let request = require('request');
…
let {username, password} = getCredentials("us");

request.post(url, function (error, response, body) {
 if (!error && response.statusCode == 200) {
   let user = JSON.parse(body);
   lambda_handler(createEvent(user.access_token), null, (error, result)=>{
    console.log(result);
    console.log("DONE - lambda_handler");
   });
 }
});

In this article, we covered building barebone Amazon Alexa skills with modern JavaScript. While the code built as described in the previous installment and this post should be fully functional, provided that developers implement intent handlers, we still have not covered in detail the serverless framework configuration. We will do in our next article.

Categories

More from our blog

See all posts