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>

Tuesday, November 8, 2016

Hook a Raspberry Pi to the Amazon Dot

Earlier this year Hackster.io had a contest looking for good internet of voice examples using a Raspberry Pi and an Amazon Echo <link>; one guy Nick Triantafillou entered with a simple weather application that would have the Echo say the current temperature and humidity taken from a DHT22 connected to his Pi <link>. This sounded like the perfect place to start including an Amazon Dot (or maybe a few of them) into my house infrastructure. I'm not interested in the latest Twitter stuff, I don't follow Facebook and the streaming services  I care about are already handled by my Roku, so this should be an interesting way to get my feet wetter in dealing with voice. I already tried voice on the Pi to try and control things, that was an abysmal failure <link>, and it would certainly be a fun thing to have around the house. So, I started stepping through his article. I like Nick's style and his instructions were clear enough to follow, so I just skipped the stuff about how to hook something to the Pi and got into the meat of hooking a Pi into Amazon AWS.

Go to https://aws.amazon.com/ and sign up for AWS;  remember, this stands for Amazon Web Services and you may have already done this in the last post. Once you get in there you'll find a screen that shows the various services they provide. For now, lets take a look at what the charges are, as in money you will have to pay them to use this stuff. If you look around on the page, there is a selection for AWS Iot (Amazon Web Services Internet of Things), and if you go there, you can find the pricing. It's a bit convoluted to find, as pricing always is for web services, so here's the link to the page I found on it:


The prices they charge for stuff after you have used it for 12 months are here. Yes, it's free for the first year and after that they start charging you for the various services. However, look at the actual prices for the various features and breathe a sigh of relief since they charge in MILLIONS of transactions. I calculated that, if I use their services the same way I use them at home, it'll cost me a few cents a month. I really mean a few cents, maybe a quarter if I'm really pounding on the house. Probably more like a nickel or so a month. Heck, even I can afford that, and the first year is free. So, if you don't like it, cancel it before they start charging you.

At any rate, you've signed up for AWS and you can get the super secret stuff you need to go ahead with the configuration. If you don't like it, don't use it; they shouldn't charge you for just signing up and playing around, at least for the first year.

The very first thing you're going to need is a 'user'. To create a user that will be able to authenticate and use the various features, you use another Amazon Web Service IAM (Internet Access Management). This is where you'll poke in some permissions that will allow your Pi to assume the identity of the user you create and then upload temperature readings and such. At the AWS console, take a look around at the services and choose IAM and go there. Somewhere on that page is a button to create a user. These things move around on the screen depending on various criteria, so I can't tell you exactly where it will show up, but when I do it, it looks like this:


The 'alexaControl' user is the one I created to do things with. Create your own and you'll get a screen that looks something like this:


Hit the 'Create' button and you'll move to the next screen:


NOW, THIS IS IMPORTANT, click on the show credentials link. There is a secret shown that you won't be able to get again. You'll need both of the items in the next steps to create the thing. I took a screen shot so I wouldn't mess up entry of the items. I also did a copy and paste into notepad so I could copy them out. This is what they look like:


I didn't download the credentials.  Close the screen and you'll see your new user, click on it to get a screen where you'll assign various permissions and such. You don't need a 'group', but you do need to add some permissions. Click on the permissions tab and you'll get something like this:


Permissions in AWS are handled by 'policy' documents that are JSON files that are read when something needs to be done that may have to have a permission. Attach the three policies you see in the screen shot above. That gives pretty much unlimited permission to the stuff you're going to create. You can always come back later after it works and whittle down the permissions to what is actually needed, but it's much easier not to have to fight those items when you're first trying to get it working.

After you look around a bit at what the other items are, you can leave this part of AWS and, start looking for the AWS IOT console. We won't be using it yet, but it's good to have it up so you can check it after the next part.

I mentioned last post that I stepped through the creation of a thing using the web based tools that Amazon provides, but then I ran into trouble figuring out how to get the stuff I needed on my Pi so I could actually use it. That's where Nick's article came in handy. He has a step by step implementation of the items needed. Of course, I didn't do it exactly the same way he did, I used what I wanted to have. So, let's step through creating a thing and the various certificates we need on the Pi:

First, create a new directory somewhere and 'cd' into it; you'll be creating files there and you want to be able to find them later. Load the huge hunk of software that Amazon provides as an interface to your machine. I simply used the defaults and let it install:

sudo pip install awscli

This will load the python version of the AWS Command Line Interface that we'll be using. Next, you want to configure it, the command is:

$ aws configure

Which will prompt you for the security items I told you to save above. It will also ask you for the 'region'. THIS IS ALSO IMPORTANT, use 'us-east-1' for the region. AWS has data centers globally, and normally, you'd choose one close to you, but for Alexa, you must choose us-east-1 which may show up as N. Virginia in a few places on Amazon.

Now aren't you glad you listened to me and saved the secrets from above? Now the steps you run through on the Pi to get it ready to connect:

Create  a 'thing'

$ aws iot create-thing --thing-name "house"

I used the name 'house' for mine, yours could be anything you want.

Now you can list it and check that the command worked:

$ aws iot list-things
{
"things": [
  {
    "attributes": {},
    "thingName": "house"
  }
]
}

You'll need to use encryption, so create certificates and keys

$ aws iot create-keys-and-certificate --set-as-active \ 
--certificate-pem-outfile cert.pem \ 
--public-key-outfile publicKey.pem \ 
--private-key-outfile privkey.pem

This will have a lot of output to the screen, you can safely ignore it because it also creates files in the directory you're using that you will need later.

4. Get the certificate ARN which is the AWS Resource Name. You know you're in acronym hell when you're using acronyms made from acronyms. The ARN is going to be needed later, I highlighted it below:

$ aws iot list-certificates
{
"certificates": [
  {
      "certificateArn": "arn:aws:iot:us-east-1:926342229229:cert/58debbfe90fa58cad4df8426bdf3f20a71df7644437203e231b503b994dfb8f3",
      "status": "ACTIVE",
      "creationDate": 1467893789.688,
      "certificateId": "58debbfe90fa58cad4df8426bdf3f20a71cf7644437203e251b503b994dfb8f3"
  }
]
}

Word wrap kinda messed up the copy and paste above, but you get the idea. Download the root certificate from symantec. You'll need a root certificate and we get that from Symantec.com. If you want to know what a root certificate is and does, go look it up; the explanation is complex. To get it on the Pi:

$ wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -O aws-iot-rootCA.crt

Now's a good time to discuss the certificates and keys you've accumulated. It's also a good time to round them up and keep them in a place you can remember and protect. Here's the list of my keys:

$ ls keys
aws-iot-rootCA.crt  cert.pem  privkey.pem  publicKey.pem

You should have all of them as well. I keep them in a directory called 'keys' and will soon limit access to them to the root user.

Now we need to create a policy that will allow your Pi to do things by giving it permission. You do this by creating a file that you will feed to aws and it will, in turn send it up to AWS on Amazon. The file contents:

{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action":["iot:*"],
        "Resource": ["*"]
    }]
}

Just use an editor, copy the stuff between the braces (include the braces at the beginning and end) and name the file something you'll recognize. The date in there is important, it's the version of software at Amazon that is used, don't change it. I followed Nick's suggestion and called it 'PubSubToAnyTopic', then use the file in the next command:

$ aws iot create-policy --policy-name "PubSubToAnyTopic" --policy-document file://iotpolicy.json

You'll get back:

{
    "policyName": "PubSubToAnyTopic",
    "policyArn": "arn:aws:iot:us-east-1:704749107060:policy/PubSubToAnyTopic",
    "policyDocument": "{\n    \"Version\": \"2012-10-17\", \n    \"Statement\": [{\n        \"Effect\": \"Allow\",\n        \"Action\":[\"iot:*\"],\n        \"Resource\": [\"*\"]\n    }]\n}",
    "policyVersionId": "1"
}

Attach the principal policy you just created, using the arn highlighted from earlier:

$ aws iot attach-principal-policy --principal "arn:aws:iot:us-east-1:926342229229:cert/58debbfe90fa58cad4df8426bdf3f20a71df7644437203e231b503b994dfb8f3" --policy-name "PubSubToAnyTopic"

Now, get the endpoint that you'll send data to. This is actually a URL that accepts mqtt data and gets it into Amazon. You will need the endpoint to actually send the data; it will show up in the code as the destination for data.

$ aws iot describe-endpoint

And you'll get back:

{
    "endpointAddress": "yournumberswillbehere.iot.us-east-1.amazonaws.com"
}

Now you have a place to send data to and a policy to handle the data. You created and named a 'thing' and it's ready to use.

But, how the heck can you tell? Go to the AWS management console; you should be getting good with the names of things by now and sign in. Look at the black bar at the top, it should be 'N. Virginia' since we set up as 'us-east-1', but if it isn't use the selection and change it. Also up at the top in the black bar is 'Services' click on it and a huge drop down of services will show up. Choose 'AWS IoT and you should get a screen like this:


There's the policy, thing and endpoint that you created using the steps above. You can click on them and see stuff related to each of them. The thing is where your shadow document will be that you'll be working with going forward.

This posting has taken hours to put together and check over, so I'm going to write about sending data from the Pi up to the thing we just created next time. 

Continued here <link>

Saturday, November 5, 2016

Amazon Dot and My Desert Home

I already warned you that this was going to get complicated and would probably drive you nuts in my first post on the Amazon Dot <link>, but you apparently didn't listen, so I'll go a bit deeper into the amazingly intricate process of hooking a Dot to my house, but first a tiny bit of background.


This complex diagram outlines the various processes and machines that get involved in a single voice request, and any piece of it can give you trouble implementing something. I got this picture directly from a nice description I ran across on Amazon, and let's start off going there to understand what is going on <link>.

Robert McCauley, the author, is unusual in that this article can be understood; at least the second or third time you read it. Ignore his comment about referring to the Amazon quick start documentation for IoT, it will only confuse you and it really doesn't talk about anything Robert doesn't. However, this article is a bit terse (meaning he passes the buck entirely) on how to use the Alexa voice service. The thing you want to learn from this is that Amazon uses mqtt to control your devices. You can actually interact with them using the AWS mqtt tool that Amazon provides and the instructions that Robert provides.

I actually created his water pump and messed with it, but I deleted it to keep from confusing myself later when I created my 'house' device. What I did was create a single device 'house' and it carries stuff like outside temperature, state of various lights, garage door position, etc. It would have been a real pain to create each of those devices and deal with them separately. The huge thing to understand here is the use of the JSON shadow device. You deal exclusively with the shadow that represents the last reported state of your device (my house). The shadow contains a 'reported' section, a 'desired' section, and a 'delta' section. For example, here is my current (as of the date above) shadow; remember it's a JSON document.

{
  "reported": {
    "humid": "37",
    "temp": "82",
    "barometer": "1016",
    "windspeed": "4",
    "winddirection": "south southwest",
    "raintoday": "0.0",
    "eastPatioLight": "off",
    "outsideLights": "off",
    "lastEntry": "isHere",
    "frontPorch": "off",
    "cactusSpot": "off",
    "outsideGarage": "off",
    "mbLight": "off",
    "gDoor2": "closed",
    "gDoor1": "closed"
  }
}

I don't have all the items I'm interested in yet, and I only voice control a few things so far, but this could be representative of anyone's implementation.  This is the reported section. If I wanted to control something, I'd put in a desired section that would contain what I wanted to change. So, if I want to turn on the outside lights:

{
  "desired": {
    "outsideLights": "on"
  }
}

That would get combined with the reported section and then inspected for differences. Any differences would show up in a new delta section. That would make the entire shadow document look like:

{
  "desired": {
    "outsideLights": "on"
  },
  "reported": {
    "humid": "37",
    "temp": "80",
    "barometer": "1016",
    "windspeed": "2",
    "winddirection": "east southeast",
    "raintoday": "0.0",
    "eastPatioLight": "off",
    "outsideLights": "off",
    "lastEntry": "isHere",
    "frontPorch": "off",
    "cactusSpot": "off",
    "outsideGarage": "off",
    "mbLight": "off",
    "gDoor2": "closed",
    "gDoor1": "closed"
  },
  "delta": {
    "outsideLights": "on"
  }
}

So you have a JSON document that holds what was last reported, what you want to change, and the difference. See why they call the sections 'reported', 'desired' and 'delta'? It actually makes sense. When you get a device hooked up to the mqtt server at Amazon, the delta portion is the only part you see coming in. That makes it easier to understand what you need to do with the actual device. So, on your Pi, or whatever, you subscribe to the Amazon mqtt service, and wait for a delta message to come in. When it does, you do what it says and report back the new status. There are a few complications in there that you'll have to deal with, but I hope to visit each of them as we go through this process.

In Robert's article, he interacts with the shadow document using Amazon's mqtt client to subscribe and publish to the shadow. That's exactly the kind of thing you will need to do in code to support your device. This stuff is handled by what's called a Lambda function. The Lambda funtion is simply code that you run on their machine that can bridge the gap between the Alexa voice service and the mqtt server to get something down to your machine to change something. In the other direction, you'll publish the latest state of your devices back so everything is kept up to date.

Now, do you understand why a little hands on with the Amazon IoT is necessary? Follow the lead from Robert's article and work with mqtt and the shadow a bit to see what is actually going on. Totally ignore his comments about Alexa, "Once you are familiar with the Alexa Skills Kit and understand how to create an Alexa skill;" That phrase is just about totally useless.

I have a suspicion that very few of Amazon's people that work on these projects actually do anything with them since their explanations leave so much out.

That's enough for you to get a feel for the interaction if you actually do it and then look at the messages that are passed in mqtt. You WILL need this when you start troubleshooting your own code.

Next post we're going to look at getting data from your device all the way back to Alexa. We won't actually try to change the state of a device because you have to have a reported section to the shadow before you can change it.

And, you probably won't need the water pump device that Robert had you create.

Continued here <link>

Tuesday, November 1, 2016

I Got One of Those New Fangled Amazon Dot Things

Obviously, this is part 1.

Yes, I took the plunge and bought an Amazon Dot before it was available and waited patiently for the release date for it to arrive. When it came in, I did the usual, "Alexa, tell me the weather." "Alexa, what time is it?" "Alexa, how tall is Scarlett Johansson?" You know, the usual.

Then, I got out the laptop and started looking at how to connect it to my house.

Let me tell you, this thing is really, really cool, but it's also the most frustrating device I have worked on in quite a while. I literally spent hours and hours on the web trying to figure out Amazon's amazingly complex and poorly documented interface to IOT (internet of things). I tried a large number of examples and tutorials out there with a singular result: they didn't work. I knew it was possible, there are youtube videos that prove it, but not a single one of them worked for me. I wrote, copied, stole thousands of lines of code and didn't get a single signal of any kind at my house. So, in a mass of frustration and disappointment, I managed to stumble (this is the third full 16 hour day of trying) across a little note on rules. It started to work part way.

Emboldened by a tiny success, I dug in and conquered the device all the way to the house, out to my patio lights and all the way back to the Alexa sitting on my kitchen counter. I had it working. No, I haven't got all the devices tied in, but I can get the weather readings taken at my house and actually turn on and off my patio light.

The problem for me, and I suspect many others, is that the interface is extremely complex and spans several of Amazon's cloud service products. For example, you speak to the Alexa and it runs many layers of recognition and permission checking that updates billing code and such before it hands something off to their Lambda service. The Lambda service runs some code that hands things off to their AWSIoT service who does some stuff and passes your signals off to Amazon's own MQTT service which will hand things off to your code at home. Your code reads this and does something returning it back to Amazon's MQTT, which passes it back to Lambda who passes it back to the Alexa service and then back to the device on the counter to be played back. In the middle of this are many, many layers of authentication, roles and permissions that have to be exactly right before things are allowed to pass. Heck, you can't even log anything without using their cloud logging service ... and you have to read it there as well.

Just forget about it being easy; it isn't. Maybe someday it will be, but it isn't right now. And, NO, I'm not going to add to the huge number of tutorials out there with my own version. Frankly, I really don't want to step through it all over again taking a hundred screen shots that will become obsolete in three or four days when they change the user interface to the various services all over again. One of the biggest problems I had was trying to work from some of the examples that were of an older interface and try finding similar capabilities on what I was looking at. You see, they have crews of people working on each piece expanding its capabilities, and even though they are very careful, things get missed. That of course means they have to fix the thing they broke and that can change the interface. It's one little guy out in the desert vs. hundreds spread around the world.

Let's talk about a specific example. Below is the session where I created the various devices using Amazon's CLI (command line interface) that I had to download and install on my Raspberry Pi. After installing it I used instructions I found out there on that world wide web thingie, and this is the console log I kept of the process. I annotated it to tell you what was actually happening after I completed the process, and it should give you a real example of what to do to set yourself up in the very beginning on the Pi:

#
# Configuring AWS ... but
# I kept the secrets that were produced by the user creation so I could use
# them here, but I forgot the step of adding permission for the user I created
# This is what happens when you forgot to add permissions to the user
#
pi@housemonitor:~/src/alexa$ aws configure
AWS Access Key ID [None]: ***Secret Stuff***
AWS Secret Access Key [None]: ***More Secret Stuff***
Default region name [None]: us-east-1
Default output format [None]:
#
# This was trying to create a 'thing' but didn't have permission 
# to do it.
#
pi@housemonitor:~/src/alexa$ aws iot create-thing --thing-name "house"

An error occurred (AccessDeniedException) when calling the CreateThing operation: User: arn:aws:iam::704749107060:user/alexaControl is not authorized to perform: iot:CreateThing on resource: arn:aws:iot:us-east-1:704749107060:thing/house
#
# After I went back and added:
#   IAMFullAccess, AWSIoTFullAccess and AWSLambdaFullAcess 
# to the user I created
#
# Then I created a 'thing' called "house"
#
pi@housemonitor:~/src/alexa$ aws iot create-thing --thing-name "house"
{
    "thingArn": "arn:aws:iot:us-east-1:704749107060:thing/house",
    "thingName": "house"
}
#
# And proved it was there by listing it
#
pi@housemonitor:~/src/alexa$ aws iot list-things
{
    "things": [
        {
            "attributes": {},
            "version": 1,
            "thingName": "house"
        }
    ]
}
#
# Custom Alexa "things" are all about security, so I generated keys
# which sent the keys to the console and scrolled the screen a bunch
#
pi@housemonitor:~/src/alexa$ aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile cert.pem --public-key-outfile publicKey.pem --private-key-outfile privkey.pem
{
    "certificateArn": "arn:aws:iot:us-east-1:704749107060:cert/e325f7755f0a4f11a59416ab7af30e20023f2c411233ecb5d2c68285843f94a4",
    "certificatePem": "-----BEGIN CERTIFICATE-----\nbunch of stuff\n-----END CERTIFICATE-----\n",
    "keyPair": {
        "PublicKey": "-----BEGIN PUBLIC KEY-----\nbunch of stuff\n-----END PUBLIC KEY-----\n",
        "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nbunch of stuff\n-----END RSA PRIVATE KEY-----\n"
    },
    "certificateId": "even more stuff"
}
#
# But, they were in the directory I created for this
# I proved it by listing them
#
pi@housemonitor:~/src/alexa$ aws iot list-certificates
{
    "certificates": [
        {
            "certificateArn": "arn:aws:iot:us-east-1:704749107060:cert/e325f7755f0a4f11a59416ab7af30e20023f2c411233ecb5d2c68285843f94a4",
            "status": "ACTIVE",
            "creationDate": 1477339839.64,
            "certificateId": "e325f7755f0a4f11a59416ab7af30e20023f2c411233ecb5d2c68285843f94a4"
        }
    ]
}
#
# Here's the list of them
#
pi@housemonitor:~/src/alexa$ ls
cert.pem  privkey.pem  publicKey.pem
# These are the certificate, private key and public key. Keep
# the private key protected and use the public key for communication
#
# This is downloading the public key for Amazon AWS IOT
# I need this to encrypt the traffic to Amazon
#
pi@housemonitor:~/src/alexa$ wget https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -O aws-iot-rootCA.crt
--2016-10-24 13:14:47--  https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem
Resolving www.symantec.com (www.symantec.com)... 104.100.196.23
Connecting to www.symantec.com (www.symantec.com)|104.100.196.23|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1758 (1.7K) [text/plain]
Saving to: ‘aws-iot-rootCA.crt’

aws-iot-rootCA.crt  100%[=====================>]   1.72K  --.-KB/s   in 0.001s

2016-10-24 13:14:49 (1.25 MB/s) - ‘aws-iot-rootCA.crt’ saved [1758/1758]
#
# Now I take a look at the directory to see what keys I 
# have accumulated so far
#
pi@housemonitor:~/src/alexa$ ls
aws-iot-rootCA.crt  cert.pem  privkey.pem  publicKey.pem
#
# This is where it starts to get even more complicated
# I have to have a policy in place to allow this Amazon user
# to publish data. The policy is a JSON formatted file I called
# iotpolicy.json (I totally stole that name from Nick Triantafillou)

pi@housemonitor:~/src/alexa$ aws iot create-policy --policy-name "PubSubToAnyTopic" --policy-document file://iotpolicy.json
{
    "policyName": "PubSubToAnyTopic",
    "policyArn": "arn:aws:iot:us-east-1:704749107060:policy/PubSubToAnyTopic",
    "policyDocument": "{\n    \"Version\": \"2012-10-17\", \n    \"Statement\": [{\n        \"Effect\": \"Allow\",\n        \"Action\":[\"iot:*\"],\n        \"Resource\": [\"*\"]\n    }]\n}",
    "policyVersionId": "1"
}
#
# Now that a policy has been put in place up on Amazon AWS
# I have to attache it to the arn (Amazon resource name) which is 
# actually saying to attach it to the place I'll eventually send it 
# to
pi@housemonitor:~/src/alexa$  aws iot attach-principal-policy --principal "arn:aws:iot:us-east-1:704749107060:cert/e325f7755f0a4f11a59416ab7af30e20023f2c411233ecb5d2c68285843f94a4" --policy-name "PubSubToAnyTopic"
pi@housemonitor:~/src/alexa$
# I get the endpoint that I'll send data to. This is actually a
# URL that accepts mqtt data and gets it into Amazon
# I will need the endpoint to actually send the data
# This endpoint will show up in the code as the destination for data
pi@housemonitor:~/src/alexa$ aws iot describe-endpoint
{
    "endpointAddress": "ayccly8mhj4t3.iot.us-east-1.amazonaws.com"
}
#
# So I have a place to send data to and a policy to handle the data,
# I need to attach that to the 'thing' I created called 'house'
#
pi@housemonitor:~/src/alexa$ aws iot attach-thing-principal --thing-name "house" --principal  "arn:aws:iot:us-east-1:704749107060:cert/e325f7755f0a4f11a59416ab7af30e20023f2c411233ecb5d2c68285843f94a4"
pi@housemonitor:~/src/alexa$
#
# Finally, the linkage to aws is done I just have to test it.
# wish me luck

This little set of steps didn't seem to bad, but I didn't understand much of it at first. That seems to be the general rule, you poke at it until you get something to work sort of, and then you put out a tutorial or example and explain things about half way because that's all you really understand. As an example, there is a line down near the bottom of the above about attach-thing-principal. This is one line in a json file that the Amazon service holds that needs to be there. They call it a principal and you have to create it with a command they provide in the CLI. It's one line, they make it sound like something important. Well, actually it is, but only because they made it important. The Amazon interface is full of things like this.

They came up with a rather clever idea. They hold the state of your device in a regular old json file on their server. No, it isn't a database, it's just a file. This is clever because they can modify the file to represent things as they change. When I was working on getting my weather data up to Amazon I was able to add one item at a time and watch the file change as I added them. I started with the temperature (naturally) and it showed up in the file. Then I added the barometric pressure and saw it show up. I stepped through the items I wanted to check on until I got a nice little weather report I could ask for while walking around the kitchen.

Once I got that working pretty nicely, I wanted to actually change something, and this is where the way that they handle the file became really useful. You get the device to be reported and it shows up in the JSON file, then you tell alexa to change it and the file changes to show new things. It has a 'desired' entry where the new state of the device shows up, and then a 'delta' entry where the difference from the overall status and the 'desired' show up. Only the delta is sent to the Pi for it to act on. You don't have to worry about what went on before at the Pi, you just do whatever the 'delta' tells you to.

This may not sound like much, but it allows the device to go off line for periods and come back to catch up with what you last told it to do. It also provide readings for the last time the device was on line. They call this entire mess a 'shadow' and I learned to like it.

So, don't despair, over the next (however many) postings I'll tell you about parts of the system that you can step through to get your own house hooked into the Alexa. But, NO, I won't take a look at your individual implementation and show you what's wrong, nor will I answer basic questions like how do you sign on to Alexa. That stuff changes almost as fast as I could write about it. Get good at bookmarking the various pages you use on Amazon, they're hard to find a second time. Remember, I'm learning about this as I go, just like you are.

The difference is that I already put over a week into the darn thing and have actually got a light I can control with it.

Oh, before you folk ask why I went my own way instead of using Smart Things or one of the other home automation systems out there. I hate cloud services. I don't want my data stored somewhere else under someone else's control and subject to their whim. If Amazon changes their policy, I just don't use them for remote control anymore; I get out the phone and touch the screen instead. The Alexa is cool, but it won't control me or my data.

Continued here <link>