Monday, November 21, 2016

Amazon Dot: Let's Turn Something On

Last post <link> we got data all the way from a Raspberry Pi up to Amazon and back to the Dot and it was able to say,  "The temperature is 79 degrees, " and we were excited. What, you got an error? How the heck do you troubleshoot this thing?

Fortunately, there actually is a way of doing this, even with the fact that they pass complex JSON strings around to carry data from one subsystem to another. The screen you got last time:


has the json string you need to test directly into the Lamba function we created in a previous post <link>. Simply copy the entire 'Lambda Request' and then paste it into the test screen for the Lambda function. I know this description leaves a lot of questions, so let's look a bit closer:

First copy the entire text from the Lambda Request shown above, then go to the Lambda Management Console:


Then select Actions -> Configure Test Event


They don't have a blank test event or something really close to what you need, so just chooses the first one 'GetNextFact' and replace ALL the text inside the edit box with what you saved above. This will replace the default with what you want. Save it; you have to scroll down on the page to get the save button to show.

Now, you're ready to actually feed the JSON to your Lambda function. You can now press the 'Test' button and see what happens. I can't really be much help on specific errors because I can't know in advance what kinds of problems you'll hit, but there should be enough information to get you started.

You can also edit the test event you just created to test other things, I used one test event to test most of my information requests; things like 'temperature', 'rainfall', by just changing the intent name inside the JSON string.

Now you have some hope of being able to debug this thing, so let's walk through adding control of something from the Dot. This time I'm going to start with the voice interaction and work down to the Raspberry Pi at my house instead of the other way around. The reason for doing it this way is because we now have the basic infrastructure in place and adding things is easier from the Dot back to the Pi. We'll start at the Alexa Interaction Model which looks like this:


There needs to be an intent added and I'm using the one I added first to my setup. The intent is ControlEastPatioLight and it has two 'slots', on and off, so I named the type onOff. Very clever of me. Just below the intent window, I added a new slot called (duh) onOff:


And the contents of the slot is:


What's happening is that Alexa doesn't send the actual text that is spoken, it sends intents which may contain enumerations to the Lambda function. Enumerations are simply things that can be chosen from a pre-defined list. In this case the two slot values I chose were 'on' and 'off'. I could have used 0 and 1, or Fred and Mary, it doesn't matter what they are, what matters is that they make sense to you because you have to use them later in actually controlling your devices.

Now, for the utterances, I used the simplest ones I could think of:


I highlighted the two above that I use for the light. The '{state}' refers back to the intent I created above and will be replaced with either on or off from the slot definition based on what you actually say to the Dot. Alexa actually parses through the sound and inserts its best guess about you said into the JSON string that will be sent to the Lambda function. So, save this work and go test it in the test screen:


Your Lambda Response will be an error because you haven't added the code yet, but the test generated the Lambda Request you'll need to test the new capability you're adding, so copy it out of the Request window and go over to the Lambda function and paste it into the test configuration before it gets lost:


It's not ready to test yet since we don't have any code to support it, but save it now for uses later. We're going to add code now to support the new intent. In the code window which you will see after you save the  event template you want to add the code. The window is small and hard to use, but you can scroll all the way down to the bottom of the little code window and there will be a button to expand it to full screen, I use this to edit since there just isn't enough visible to get proper context for changes:


The button is in the image above over on the right; I put the cursor there so you could find it. I searched for this for about 20 minutes the first time.

Add a couple of lines into the intent handling to get you to a routine that will format the mqtt request that the AWSIoT needs:


I highlighted the lines in the picture above. Now you need the code to create the mqtt request:


It just barely fit on the screen. Since you've already done this once, it should be easily recognized with the difference being the payload object and a call to updateThingShadow(). I think it's pretty intuitive what is going on here. A JSON fragment string is created with the content of the slot created above and passed off to the shadow as a 'desired' item. Now you're starting to understand why I described the operation of a 'Device Shadow' so carefully back a few posts ago <link>; the interaction of the shadow and the code on the Raspberry Pi is very important to getting this stuff working.  I know how big a pain in the bottom it is to read from a post and try to type it in, so here's the code above you can copy and paste with:

function controlEastPatioLight (intent, session, callback) {
    var repromptText = null;
    var sessionAttributes = {};
    var shouldEndSession = false;
    var speechOutput = "";
    var newstate = intent.slots.state.value;

    //Set the light to 0 to turn it off
    var payloadObj={ "state":
                        { "desired":
                            {"eastPatioLight":newstate}
                        }
                 };
    //Prepare the parameters of the update call
    var paramsUpdate = {
        "thingName" : config.IOT_THING_NAME,
        "payload" : JSON.stringify(payloadObj)
    };

    //Update Device Shadow
    iotData.updateThingShadow(paramsUpdate, function(err, data) {
        if (err){
           console.log(err, err.stack); // an error occurred
        }
        else {
            speechOutput = "The east patio light has been turned " + newstate + "!";
            console.log(data);
            callback(sessionAttributes,buildSpeechletResponse(intent.name, speechOutput, repromptText, shouldEndSession));
        }    
    });
}


Be sure to change the various names to something you'll understand later.

Once again, we don't actually send a command to the Pi to change something, we send a desire. That desire is pulled off the mqtt queue and used to do the actual change, then we publish back the new state. But, if you get confused, go back and read the explanation I linked to in the last paragraph.

That's it for right now, the next post has a bunch of code in it for the raspberry pi to get the desire and do something about it. It's going to get confusing to both read and for me to write, so next post.

Have fun, and feel free to work ahead, you can actually see the desire created and sent over in AWSIot if you want to.

The next post in this series is here <link>

Wednesday, November 16, 2016

Amazon Dot, Let's Connect Alexa to the Lambda Function

Last Post <link> we did one more of the seemingly interminable steps to voice control in our house, the Lambda function, but we couldn't test it because we didn't have the proper input for it.  So, let's get into Alexa and create a voice service so we can actually start talking to it.

For those of you that jumped into the middle of this project, Alexa is the Amazon Voice Service that is the engine behind the Dot, Echo, that silly little switch they just came out with and probably more devices over time. It works pretty well and is actually pretty awesome for picking up voice in a house. I want it to control the items in the house with voice commands, and this is my latest attempt.

The really, really good news is that this is the last chunk of stuff we have to take on before we can get data from the house. We still have to work through the steps to send data, but all the major pieces will be there and we will get to just expand the capabilities.

So, sign into the Amazon Developer Console <link> and look up at the top black bar for 'Alexa' and click on it.


This is a tiny bit tricky because it looks like you want the 'Alexa Voice Service', but you really want the 'Alexa Skills Kit'. The voice service (in this case) is really meant for manufacturers to create interfaces for their devices. It's where folk like Belkin and Philips go to do things. If you have a hundred or so developers to work on this and relatively unlimited funds, by all means, go for it. If you're basically me, one guy trying to do something cool to have fun with, you definitely want the skills kit, so click on it.


This is what it looks like AFTER you create a skill, you want to 'Add a New Skill', which will take you to the next step:


Fill this in and click next. Remember, you want a 'Custom Interaction Model'. But, some advice on the 'Invocation Name', keep it short. Don't use something like, "Bill's really cool interface to the brown house at the corner," because you'll be saying this over, and over, and over. I used "Desert Home" and it's too darn long. 


And here you are at the Interaction Model screen. Now you get to actually do some cool stuff. The top box is for a rather simple set of JSON (of course) strings, and the bottom is for 'utterance' that will use those JSON strings to create requests that will be spoon fed to the Lambda function we just created. Here's the intents for the Lambda function we created previously:


It's a JSON string with a table of  'intent' entries. Now we want to scroll down a bit to put in some utterances:


And 'Save' them. The save function will actually create the model which means you may get an error. So far, the errors I've had were relatively self-explanatory. Things like misspelling a word or forgetting a comma. Each of the lines of  the utterances start off with one of the intents you defined in the first box and the rest of it are regular words that you speak to the Dot. The more of these utterance you put in, the smoother it seems to work when you use it. For now, just put a few in, you can add to them later as you see fit. When you get it saved; I mean really saved, and see the message above, click 'Next'.


The 'Service Endpoint Type' is AWS Lambda of course because we went to all the trouble of creating a Lambda function for it, and you do not want users to link their accounts to this. Linking accounts is for selling services through Amazon. When you click on the Lamba radio button, it will open a couple of items you have to fill in:


Since Alexa only works in North America that button is obvious, the empty field is something to drive you nuts. Notice they don't give you a hint what they want in here? No help button, ... NOTHING!

What they want is the arn (Amazon resource number) of the Lambda we created last post, so go there and get it. Copy it out and paste it in here. I put mine in there so you could see what it looks like. Now, click 'Next'


You're on the TEST SCREEN !! Finally, you get to do something and see something happen. Sure, we saw a little bit happen way back when we were messing with the mqtt interface to the AWSIot, but that was way back there. This time we get to actually try some voice related stuff. Whoo Hoo.

Just ignore the silly blue message about completing the interaction model. That message seems to always be there; click the enable button and watch the glorious test screen expand:


You get a voice simulator where you can type stuff in and have Alexa speak it back to you. There's a service simulator where you can type in text and Alexa will shape it into a JSON string that will be sent to the Lambda function, which will do whatever you told it to do and send back a response (or error) that you see in the other window. If it works, there's a button down the screen that will allow you to play the text from the response. Here's what one interaction (that actually worked) looks like:


The Lambda request window is where you can copy the JSON to use in testing the Lambda function directly, you just configure a test, copy that stuff in and go for it. 

You DON'T want to publish this. If you publish it, you'll be allowing whoever hooks their Dot or Echo to this skill to mess with your house. It'll work just fine with your Dot, go give it a try once you work out whatever kinks that might have kicked in.

So, now we can get the temperature from our house by voice. Next we want to actually control something. That will mean changes to the voice model, the Lambda function and the code on our Pi.

Bet you can't wait for the next installment where I'm going to discuss a little bit about debugging the Lambda function and start down the path to changing something using voice commands to the house. 

Have fun.

The next post in this series is here <link>.

Saturday, November 12, 2016

Amazon Dot Lambda function

Well, we've created a 'thing' and managed to send data from our Raspberry Pi over the internet to it and can see it in the AWSIoT console. Now we'll build a 'Lambda' funtion to interface between the shadow document and the Alexa voice service. The Lambda server is yet another cloud service offered by Amazon, and yet another, step in getting the data all the way back to us on the Amazon Dot. This is where we get to write some more code, this time in node.js.

I'm not going to teach you node.js; mostly because I don't know it all that well, and there are several thousand other sites that can help you with this part of the project. What I will do is show you the first bit of node.js code I put together for my project. This is another piece that is more than just an example; I ran this code for a couple of days getting my feet wet in handling this stuff.

So ... login to the Amazon AWS console. Yes, you've been here before, and you'll be here several more times getting this working. I can't show you a picture of what it will look like for you since this is another one of those pages that change as you do stuff and get more bits of the project implemented. I can show you what it looks like for me:


Look around the screen and find 'Services', it's most likely up in the top black bar like this screen shows, and select it. You'll get the drop down menu of services and look for 'Lambda'. When you get to the next screen, you want to 'Create a Lambda Function'; it should be a big blue button. You be prompted to choose a blueprint for the function. You want to use Node.js 4.3 and you can filter on 'Alexa' to see what they offer for blueprints. I recommend just choose the 'Blank Function' from the menu and use the code I have placed below.

Now, since web postings hang around for years and years, look at the date of this post and remember that things have probably changed a lot since I wrote it and make an informed decision.

Anyway, the next screen wants you to configure a trigger. A trigger in this case is what will be calling your Lambda function, and you want Alexa to be the thing that activates the new function you're going to make. On the screen you'll see a rounded, dashed box beside the word 'Lambda', click there.


That will give you a drop down menu, choose 'Alexa Skills Set', NOT 'Alexa Smart Home'. 'Alexa Smart Home' is an even more complex interface to transfer data that was designed for businesses to implement their various smart devices into the AWS environment. I went that way at first and was driven to the brink of tossing the Dot back in the box and shipping it back to Amazon. I may go look at it again when my distaste subsides a bit, but that will take a while. I just couldn't get past the TON of requirements that seemed to crop up every time I tried to do something. Nope, just use the 'Skills' interface, you'll thank me later.

After you get that part done, click 'Next' and you'll get to the 'Configuration' screen. This is the place you'll do most of your work. When you get some code in and save it, you'll also get a 'Test' and other screens you can play with. For now, name your function and choose the runtime Node.js 4.3. You have other choices there including Python, but I couldn't find enough documentation on the python interface to do what needed to be done. There may be enough documentation in place when you get this far, but I gave up on finding it and just used Node.

A little lower on the screen you'll see a bit of code they supply as an example to get you started, replace that with the code I include below:

//Environment Configuration 

var config = {};
config.IOT_BROKER_ENDPOINT      = "yournumber.iot.us-east-1.amazonaws.com";
config.IOT_BROKER_REGION        = "us-east-1";
config.IOT_THING_NAME           = "house";
config.params                   = { thingName: 'house' };

//Loading AWS SDK libraries
var AWS = require('aws-sdk');

AWS.config.region = config.IOT_BROKER_REGION;
//Initializing client for IoT
var iotData = new AWS.IotData({endpoint: config.IOT_BROKER_ENDPOINT});

// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = function (event, context) {
    try {
        console.log("event.session.application.applicationId=" + event.session.application.applicationId);

//        if (event.session.application.applicationId !== "amzn1.ask.skill.yoursecretnumbergoeshere") {
//             context.fail("Invalid Application ID");
        }

        if (event.session.new) {
            onSessionStarted({requestId: event.request.requestId}, event.session);
        }

        if (event.request.type === "LaunchRequest") {
            onLaunch(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "IntentRequest") {
            onIntent(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "SessionEndedRequest") {
            onSessionEnded(event.request, event.session);
            context.succeed();
        }
    } catch (e) {
        context.fail("Exception: " + e);
    }
};

/**
 * Called when the session starts.
 */

function onSessionStarted(sessionStartedRequest, session) {
    console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId +", sessionId=" + session.sessionId);
}

/**
 * Called when the user launches the skill without specifying what they want.
 */
function onLaunch(launchRequest, session, callback) {
    console.log("onLaunch requestId=" + launchRequest.requestId + ", sessionId=" + session.sessionId);

    // Dispatch to your skill's launch.
    getWelcomeResponse(callback);
}

/**
 * Called when the user specifies an intent for this skill.
 */
function onIntent(intentRequest, session, callback) {
    console.log("onIntent requestId=" + intentRequest.requestId + ", sessionId=" + session.sessionId);

    var intent = intentRequest.intent,
        intentName = intentRequest.intent.name;

    //intent handling
    switch (intentName){
        case "GetTemperature":
            getTemperature(intent, session, callback);
            break;
        case "GetHumidity":
            getHumidity(intent, session, callback);
            break;
        case "AMAZON.HelpIntent":
            getHelp(callback);
            break;
        case "AMAZON.StopIntent":
        case "AMAZON.CancelIntent":
            handleSessionEndRequest(callback);
            break;
        default:
            throw "Invalid intent";
    }
}

/**
 * Called when the user ends the session.
 * Is not called when the skill returns shouldEndSession=true.
 */
function onSessionEnded(sessionEndedRequest, session) {
    console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId +
        ", sessionId=" + session.sessionId);
    // Add cleanup logic here
}

// --------------- Functions that control the skill's behavior -----------------------

function getWelcomeResponse(callback) {
    var sessionAttributes = {};
    var cardTitle = "Welcome";
    var repromptText = "Are you there? What would you like to know?";
    var shouldEndSession = false;

    var speechOutput = "Desert home";
    callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function getHelp(callback) {
    var cardTitle = "Help";
    var repromptText = "Are you there? What would you like to know?";
    var shouldEndSession = false;

    var speechOutput = "Welcome to Desert Home," + 
        "dave is still working on what to say here,";
    callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

function handleSessionEndRequest(callback) {
    var cardTitle = "Session Ended";
    var speechOutput = "Thank you";
    var shouldEndSession = true;
    callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}

function getTemperature(intent, session, callback, sayit) {
    var cardTitle = "Temperature";
    var repromptText = "";
    var sessionAttributes = {};
    var shouldEndSession = false;
    var temp = 12;
    
   iotData.getThingShadow(config.params, function(err, data) {
       if (err)  {
           console.log(err, err.stack); // an error occurred
           temp = "an error";
        } else {
           //console.log(data.payload);           // successful response
           var payload = JSON.parse(data.payload);
           temp = payload.state.reported.temp;
         }

        speechOutput = "The temperature is " + temp + " degrees fahrenheit,";
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

function getHumidity(intent, session, callback) {
   var cardTitle = "Humidity";
   var repromptText = "";
   var sessionAttributes = {};
   var shouldEndSession = false;

   var humidity = 12;

   iotData.getThingShadow({ thingName: 'house' }, function(err, data) {
       if (err)  {
           console.log("error back from getThingShadow");
           console.log(err, err.stack); // an error occurred
           humidity = "an error";
        } else {
           //console.log(data.payload);           // successful response
           payload = JSON.parse(data.payload);
           humidity = payload.state.reported.humid;
         }

        speechOutput = "The humidity is " + humidity + " percent.";
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
    });
}

// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
    return {
        outputSpeech: {
            type: "PlainText",
            text: output
        },
        card: {
            type: "Simple",
            title: title,
            content: output
        },
        reprompt: {
            outputSpeech: {
                type: "PlainText",
                text: repromptText
            }
        },
        shouldEndSession: shouldEndSession
    };
}

function buildResponse(sessionAttributes, speechletResponse) {
    return {
        version: "1.0",
        sessionAttributes: sessionAttributes,
        response: speechletResponse
    };
}

Nope, I'm not going to leave you in the dark; let's step through this code to see what the heck is going on. I won't cover every detail, but I promise to hit all the important parts.

It starts off with a boilerplate that you have to have to get various libraries and such into place and the first items you have to change are:

config.IOT_BROKER_ENDPOINT      = "yournumber.iot.us-east-1.amazonaws.com";
config.IOT_BROKER_REGION        = "us-east-1";
config.IOT_THING_NAME           = "house";
config.params                   = { thingName: 'house' };

Broker endpoint is taken from AWSIoT which we dealt with in the previous posting <link>, and looks like this:


The number you want is in the upper right of the black section and you want only the portion after the 'https://' through the 'com'. Something like: yournumber.iot.us-east-1.amazonaws.com .  Broker_region must be "us-east-1", since that is the only endpoint that currently supports Alexa. Thing name is whatever you named your thing from my previous post, and the last bit is some JSON that will be needed later in the code; just replace 'house' with whatever you named your thing.

Moving on down you'll see the exports handler. This is the actual entry point into the Lambda function and is boilerplate, just use it until you get a feel for what is going on. Later, you may want to change it to do something special, but let's leave it alone for now. One thing I want to mention here is the two lines that are commented out:

//        if (event.session.application.applicationId !== "amzn1.ask.skill.yoursecretnumbergoeshere") {
//             context.fail("Invalid Application ID");

These lines stop the handler from going any further and return an error if the 'application-id' is wrong. There's the possibility that your function could get something intended for some other function and these two lines stop anything bad from happening in that case. However, we don't know what the appliation-id is yet, so comment this check out for now. The application-id can be read once it is working and put into the statement.

A little further down is onSessionStarted and onLaunch, they just log some stuff and Launch calls welcome, we'll talk about it in a bit. Then, you get to onIntent; this is where the work actually starts.

An 'Intent' is nothing more than an entry in a JSON string that is sent to the Lambda function. When I finally get to creating the voice interaction, you'll see that you create this intent which gets forwarded to the Lambda function. That's why the code is relatively simple to understand. This little bit of code illustrates it:

switch (intentName){
        case "GetTemperature":
            getTemperature(intent, session, callback);
            break;
        case "GetHumidity":
            getHumidity(intent, session, callback);
            break;

It's simply comparing the contents of one item in the JSON string sent to a string you defined in the voice interaction stuff that is coming later. In the example I started with there was a rather involved if-then-else construct here that I kept getting lost in, so I changed it to a switch statement so I could keep track of what was supposed to happen; feel free to rebuild it into whatever you want. I happen to like this.

The code that follows is where you get to gather the data from AWSIOT, compose a voice response and hand it back to be sent to the Dot. The fun stuff happens in this code, so to get an understanding of how to work with it, take a closer look at getTemperature().

function getTemperature(intent, session, callback, sayit) {
    var cardTitle = "Temperature";
    var repromptText = "";
    var sessionAttributes = {};
    var shouldEndSession = false;
    var temp = 12;

This is just basic variable set up for a little further down. I intentionally set the temperature to 12 so I could see it change when I get it from the shadow document.

   iotData.getThingShadow(config.params, function(err, data) {
       if (err)  {
           console.log(err, err.stack); // an error occurred
           temp = "an error";
        } else {
           //console.log(data.payload);           // successful response
           var payload = JSON.parse(data.payload);
           temp = payload.state.reported.temp;
         }

This uses one of their interface functions to get entire 'reported' section of the shadow document and parse out just the payload portion. If there's an error, it just sets the temp variable to 'an error', but with no error it gets the temperature you sent up. 

        speechOutput = "The temperature is " + temp + " degrees fahrenheit,";
        callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));

And here is where you tell the Dot what to say, and it's pretty obvious what I did here for the message. The 'callback' routine is how you give control back to Alexa and get out of the Lambda funtion.

I put the humidity function in so you could see how to handle more that one intent. It's the same as temperature. The last two routines handle stuffing your possible responses into a JSON message that is returned to the Alexa code; they make things easier for you and are part of the boilerplate.

And, there you have it, the horrifying Lamba function explained. Look up the page, save it, and we're ready to test it. First though, look at the page for the tabs, Configuration, Triggers and Monitoring. Triggers should already be filled in for you, but double check, it should look like this:


If it doesn't, you need to add in the Alexa Skills Kit as above. The Monitoring tab has cool graphs that are useless at this point. You may want to use them later, but wait until you get something working to play around in there. The Configuration tab needs to be checked, and there's a really important item in there that we have to attend to. First, here's what the screen looks like:


Double check runtime and handler. The one that will probably be messed up is role; you need to select an existing role, but the existing role doesn't exist yet. I fought this forever trying to get things to work. There were several examples out there on the web that showed something for this, but they were wrong. They were correct in how to set the role up, but not the actual permissions needed.

Open another window in the browser and go to AWS IAM, remember that from your previous work in defining the user? Once in the IAM console, select 'Roles' from the left hand side and you'll want to create a new role. Once again, a role is a JSON document that is read to allow permission to do things. You do not want to attach a policy, instead, create an inline policy. Here's the screen I get for this, but remember the screens change, look for similarities:


And, here is the JSON for the policy I came up with that finally worked:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iot:*",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iot:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
I had to add a separate section to the JSON to allow access to all resources for AWSIot. Most of the examples out there had this all shoved into one section and it just didn't work. Save your policy, give it a name you can remember and go back to the Lambda function and select the (now) existing role you just created. Yes, you may have to refresh the screen to have it read the new role, but you should be used that kind of thing.

Go back to the 'Code' tab and there should be a 'Test' button up at the top. You're not quite ready to test it because you have to create a test event to do anything. There are some examples of test events, but they don't cover enough possibilities to really do you much good. The best way to get a test event is to use the Alexa voice service to create one, but I haven't talked about that yet.

In the next post we'll visit that and create a tiny voice model that will call the lambda function. That will give you a test event that you can plug in and work with. Then you can expand on all of this to your heart's content.

Talk to you next time.

The next post in this series is here <link>

Wednesday, November 9, 2016

Amazon Dot, Sending Data From Raspberry Pi

Last post <link> I showed you how to create a 'thing' and to get the necessary certificates and keys to enable communication from the Pi up to the Amazon AWS; now we need to look into sending some data up there. I tried the Amazon library that is supplied with the AWSIot, but frankly, it had a bug I couldn't tolerate as well as really pretty, but incomplete documentation. The bug is that you can't run two instances of it on a Pi. If you start up a second instance, the first one starts to fail, and I want to have more than one thing that can send data. The documentation only shows how to use it with Java and Javascript; the python descriptions are just not there. Also, each piece of it leaves things out that you need and I didn't want to spend even more hours experimenting.

The tutorials helped some, but like I said, not a one of them actually worked when I tried them, so I used Nick's example from my last post and built a process that could send data up, then I converted it to use simple mqtt calls and simplified it a whole lot. Nick's example can't receive data from Amazon, so that part had to wait until I got some experience.

To send data from your Pi up to Amazon is actually pretty simple, so let's step through some code to illustrate it. First, you have to have mqtt client installed. You don't actually need mqtt server unless you already use it for your stuff, so install mosquito. You can refer back to my post where I discovered how easy to use mqtt actually is for my instructions <link>, or use one of the hundreds of others out there. Once you've got it in place and tested a bit, you're ready to put something together that will send data up to Amazon AWSIot; yep, I'm going to use the acronyms; you should be used to them by now.

#!/usr/bin/python
import os
import sys
import time
import paho.mqtt.client as mqtt
import ssl
import json
import pprint
from houseutils import timer, checkTimer

pp = pprint.PrettyPrinter(indent=2)

def on_awsConnect(client, userdata, flags, rc):
    print("mqtt connection to AWSIoT returned result: " + str(rc) )
    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed. You still have to do the
    # reconnect in code because that doesn't happen automatically
    client.subscribe ([(awsShadowDelta , 1 ),
                      (awsShadowDocuments, 1)])
                      
# If you want to see the shadow documents to observe what is going on'
# uncomment the prints below.
def on_awsMessage(client, userdata, msg):
    #print "TOPIC = ",
    #print msg.topic
    #print "PAYLOAD = ",
    payload = {}
    payload = json.loads(msg.payload)
    #pp.pprint (payload)
    #print ""
         
    if msg.topic == awsShadowDocuments:
        print "got entire shadow document"
        #pp.pprint (reported)

    elif msg.topic == awsShadowDelta:
        print ("got a delta")
        #print (pp.pformat(payload["state"]))
            
def updateIotShadow():
    temperature = 79.1
    # Create report in JSON format; this should be an object, etc.
    # but for now, this will do.
    report = "{ \"state\" : { \"reported\": {"
    report += "\"temp\": \"%s\", " %(int(round(temperature)))
    report += "\"lastEntry\": \"isHere\" " #This entry is only to make it easier on me
    report += "} } }" 
    # Print something to show it's alive
    print report
    print("Tick")
    err = awsMqtt.publish(awsShadowUpdate,report)
    if err[0] != 0:
        print("got error {} on publish".format(err[0]))

if __name__ == "__main__":
    # these are the two aws subscriptions you need to operate with
    # the 'delta' is for changes that need to be taken care of
    # and the 'documents' is where the various states and such
    # are kept
    awsShadowUpdate = "$aws/things/house/shadow/update"
    awsShadowDelta = "$aws/things/house/shadow/update/delta"
    awsShadowDocuments = "$aws/things/house/shadow/update/documents"
    # create an aws mqtt client and set up the connect handlers
    awsMqtt = mqtt.Client()
    awsMqtt.on_connect = on_awsConnect
    awsMqtt.on_message = on_awsMessage
    # certificates, host and port to use
    awsHost = "data.iot.us-east-1.amazonaws.com"
    awsPort = 8883
    caPath = "/home/pi/src/house/keys/aws-iot-rootCA.crt"
    certPath = "/home/pi/src/house/keys/cert.pem"
    keyPath = "/home/pi/src/house/keys/privkey.pem"
    # now set up encryption and connect
    awsMqtt.tls_set(caPath, certfile=certPath, keyfile=keyPath, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)
    awsMqtt.connect(awsHost, awsPort, keepalive=60)
    print ("did the connect to AWSIoT")
    
    # Now that everything is ready start the mqtt loop
    awsMqtt.loop_start()
    print ("mqtt loop started")

    # this timer fires every so often to update the
    # Amazon alexa device shaddow; check 'seconds' below
    shadowUpdateTimer = timer(updateIotShadow, seconds=10)
    print("Alexa Handling started")

    # The main loop
    while True:
        # Wait a bit
        checkTimer.tick()
        time.sleep(0.5)

The code above is more than an illustration of what to do, it actually runs on my machine and will update the AWSIoT shadow I'm currently using. I took a lot out of it because you probably don't do the sensors the same way I do, and there's no point in showing how I read the sensor data out of the database and format it.

Let's start at the top and work our way down in the code. The first two routines, on_awsConnect() and on_awsMessage() are the mqtt callbacks that you'll use to connect, subscribe and publish to the AWSIoT mqtt server. In on_awsConnect() I subscribe to the general topic,

"$aws/things/house/shadow/update/documents"

and

"$aws/things/house/shadow/update/delta".

The 'documents' one is where you can see the entire shadow document and the 'delta' is where you derive the command sent to your code by Alexa. But, for now, we're only going to discuss the data we want to send up and that is published to the topic,

"$aws/things/house/shadow/update",

because that is the first thing you need to conquer in getting this running. I only illustrate the subscriptions here so you can get a feel for the message interaction.

Now, specifically in on_awsMessage() I publish to

"$aws/things/house/shadow/update",

a specific format that AWSIot expects, it actually looks like this,

{ "state" : { "reported":
   {
     "temp": "79",
     "lastEntry": "isHere"}
   }
}

if you remove all the "\" crap. I did it as a string (probably a mistake) to cut down on the time it was taking me to try things. The "lastEntry" was added by me to make it easier to add entries to the JSON string as I implemented different sensors. I just copied that line, pasted it back in and changed the stuff to be what I wanted to work on next.

Yes, it's just that simple to send an update message, the hard part was finding out what the format was and the specific mqtt topic to publish to.

Now, down in the main code there's a few declarations of the topics used to make it somewhat simpler to code, and then the objects for the mqtt client and callbacks. The next bit,

    awsHost = "data.iot.us-east-1.amazonaws.com"
    awsPort = 8883
    caPath = "/home/pi/src/house/keys/aws-iot-rootCA.crt"
    certPath = "/home/pi/src/house/keys/cert.pem"
    keyPath = "/home/pi/src/house/keys/privkey.pem"

are pretty much boiler plate for specifying the port you have to use, the actual server you send to and the fully qualified path names to your certificates. You got the certs from the previous example and should have saved them somewhere. The reason for the full path names is so this code can run in any directory. Port 8883 is the one chosen by Amazon, you can't change it. Then, 

    awsMqtt.tls_set(caPath, certfile=certPath, keyfile=keyPath, cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)

    awsMqtt.connect(awsHost, awsPort, keepalive=60)

cause tls encryption to be implemented (this comes free with mosquitto client) and the actual connection to the server. Next,

    awsMqtt.loop_start()

you must have an mqtt loop of some kind so the queue can be read, and awsMqtt.loop_start() is non-blocking. This is important so you can control when the updates are sent to AWSIoT. The last little bit is using the simple timer I created to keep from starting a separate thread for timing something this simple. I used to use a much more complex timer set up, but that was a waste of resources. The timer code I used is described here <link>, but of course, you can do anything you want.

The way this code works is every so often (10 seconds in this example) the timer fires and calls updateIotShadow() which formats a message holding a temperature I just hard coded in, and sends it up to a specific topic on AWSIot using my credentials. This connects it to the right place and suddenly, I have the data available on Amazon. 

Yes, that's literally all there is to sending data and having it show up there. You can verify this by  going to the AWSIoT console and looking at your 'thing'; it will have an entry for 'temp' and a value of '79'. Now what you have to do is implement all your sensors in the message which will make you realize why I put the 'LastEntry' in there, and check it out in the console on Amazon.

So, now would be an excellent time to bookmark the Amazon AWSIoT console in your browser; you're going to be using it a lot. You'll also use bookmarks to the Amazon Lambda server, and the Alexa server a lot.

Next time, We're going to work on a Amazon Lambda function to receive requests from Alexa (I'll be using an Amazon Dot) and respond with the temperature we just sent up. It'll be in two parts because it's mildly complex and it's best to take this stuff on a bite at a time.

Have fun.

The next post in this series is here <link>