Wednesday, April 20, 2016

(#24) Amazon's Internet of Things Play - continued

Houston, we have data...


Short n' sweet - it works. The AWS C SDK, for Linux, works. I know, I know, big surprise. Let's hit some of the "Lessons Learned" Highlights from my point of view.

I walked through the steps for a successful connection to AWS last time. But I've had to change some of the initialization parameters, from the samples, for this application. Specifically, I had to change the MQTT KeepAlive value.


Here are the new initialization values:


"aSystem" is a C structure I use to pass around system parameters.  It's initialized to 'sensible' defaults upon program start, and it can pull in values from command line options and/or an INI file.


MQTT KeepAlive

The symptom I was seeing is that some weather status messages were making it to AWS but then subsequent messages would fail. I'd get PUB_FAILED responses from the aws_iot_publish_message() calls. Usually the first call would succeed and the rest fail.

If you read this link on what the MQTT KeepAlive value is used for, you'll see:
The keep alive functionality assures that the connection is still open and both broker and client are connected to one another. Therefore the client specifies a time interval in seconds and communicates it to the broker during the establishment of the connection. The interval is the longest possible period of time, which broker and client can endure without sending a message.
I had used a value of 10 (10 seconds) that came from the AWS sample code. My weather station is designed to send Current Condition MQTT packets about twice a minute.  So the first message would make it through, and then the others would not because 30 seconds between messages is greater than the 10 second value I had plugged in.

Changing this value to 60 (something longer than 30) fixed the issue.


Connecting to AWS IoT

Here's the connect code:



[ Whoops - I see a bug! I set "reconnect" to true rather than use the value set in my aSystem structure. ]

Note "AWSIoTErrorToString()" is a function I wrote to map an AWS IoT_Error_t value to a readable error message.


Publishing Messages to AWS IoT 

And here's the publish:


"messageBuffer" is my JSON message, messageLength is the 'strlen()' size of that JSON message.

I peeked at the JSON APIs that accompany the AWS IoT SDK and they seem to focus on parsing. Since I'm creating a JSON message I didn't bother to dig into them further.


Send/Receive

Since I'm sending messages only, no apparent need to call "aws_iot_mqtt_yield()"


The Current Conditions Message as JSON

Recall that my old MQTT code sent the messages as just plain-text. Here's my new untested JSON formatted message:



And finally, here's a screen capture of the MQTTfx client showing Weather Status messages flowing into AWS.



So - where are we?

After a half-dozen hours, our Weather Station is now sending MQTT messages to the AWS IoT environment.


What's next?

Light bulbs!!!  Time to change my TCP Connected Lightbulb code to have control passed over to AWS and see if there's value to their Shadows.  Which strikes me, at first blush, as a reinvention of Firebase

Thursday, April 14, 2016

#(23) Amazon's Internet of Things Play - continued 

Digging into the SDK



Starting with the end, the summary is their Linux C SDK is pretty darned good. Stuff works out of the box. There. The End.  


But - again - it's not all peaches and cream. I'm working on modifying my Things now to use their SDK.  And that means work on predominantly two fronts: switching to JSON for my MQTT packets and pulling out Mosquitto (my MQTT implementation) and plugging in theirs.


I guess a relevant question is - do I need to do both? Yes and no. I can certainly switch to JSON without leveraging their SDK and I'd be willing to get I could be the Mosquitto based MQTT implementation to talk to the AWS IoT MQTT Broker.

But where's the fun in that? 

SDK Step by Step

So, to keep things simple, let's move on to the next-to-the-last chapter and talk about Lessons Learned in playing with their SDK.  We'll be following along in their Developer's Guide in the SDK section.

 
First - do download and build their SDK. Build their samples, run their samples. Convince yourself that your AWS setup is working by running their sample programs successfully. Note that I picked the "mbedtls from ARM" version of their SDK - well, just because I plan on running mostly Raspberry Pis and it looked smaller, neater.


Second - note that when you build and run their SDK samples, three TLS static libraries are created. In gleeful anticpation of linking against them later, I went ahead and moved them into /usr/local/lib:

pconroy@pconroy-VirtualBox:/usr/local/lib$ ls *mbed*
libmbedcrypto.a  libmbedtls.a  libmbedx509.a



Third note that I couldn't step (as in single step debugger) through the samples with gdb and my IDE (Netbeans).  The aws_connect() immediately failed, as did the publish. Looking at their Paho based MQTT source, it's implemented some timers and slow stepping through or even over a call was causing the call to fail.  Just set a breakpoint after any aws_iot_xxx() call and things were working again.

Note that I don't recall ever having this issue with the Mosquitto based implementation of MQTT.  That might be reason enough to switch back to Mosquitto at some point.

Fourth: I went ahead and created an AWS IoT Everything library. I took all of their SDK, dumped it into a Single NetBeans project and created a static library.  This kitchen-sink approach took about an hour, but now I have a simple library to pull into my other projects.

Again - in gleeful anticipation - I've copied the new library into /usr/local/lib and run ldconfig.

Nota Bene: if you're using gcc to compile and link, the link order of the mbed TLS libraries is important.  Link your code with those libraries in this order:
  • mbedtls.a
  • mbedx509.a
  • mbedcrypto.a

You'll get unresolved symbol errors unless you honor the above order.

Let's go to the code!

Time to pull up some code and see if we can connect! I'll start with the initialization and connection code.




And, right off the bat, I'm a bit confused (without RTFMing and without looking at the code) there seems to be some amalgamation of AWS-MQTT-ishness and Paho MQTTishness that's a bit hard to decipher. At least for me coming from experience with Mosquitto.




There are two structures that look a bit similar to me, at a glance:
MQTTClient_t        mqttClient;
MQTTConnectParams   connectionParams;

There's some overlap between the two, in terms of the callback. For now, we'll ignore the first and concentrate on the second call.

Here's what I used to successfully connect to my AWS IoT instance:

connectionParams.KeepAliveInterval_sec = 10;
connectionParams.MQTTVersion = MQTT_3_1_1;
connectionParams.disconnectHandler = AWSIoTMQTT_DisconnectHandler;
connectionParams.enableAutoReconnect = (uint8_t) FALSE;
connectionParams.isCleansession = (_Bool) TRUE;
connectionParams.isSSLHostnameVerify = (_Bool) TRUE;
connectionParams.isWillMsgPresent = (_Bool) FALSE;
connectionParams.mqttCommandTimeout_ms = 2000;
connectionParams.pClientID = "WS2308_ClientID";
connectionParams.pDeviceCertLocation = deviceCertFileLocation;     
connectionParams.pDevicePrivateKeyLocation = privateKeyCertFileLocation;
connectionParams.pHostURL = aSystem->mqtt.brokerHost;
connectionParams.pPassword = NULL;
connectionParams.pRootCALocation = rootCertFileLocation;
connectionParams.pUserName = NULL;
connectionParams.port = aSystem->mqtt.portNumber;
connectionParams.tlsHandshakeTimeout_ms = 5000;    

connectionParams.will.isRetained = FALSE;                 
connectionParams.will.pMessage = "LWT Message";
connectionParams.will.pTopicName = "LWT_Topic";
connectionParams.will.qos = 0;


Let's run through the highlighted ones and make comments:
  • Keep Alive Interval set to 10 - this was pulled from the AWS Sample Code
  • SSL Host Name Verify - must be set to True to connect
  • the MQTT Command Timeout of 2000 was pulled from the sample code
  • the MQTT Broker Host, pHostURL is the AWS IoT MQTT Address and can be found by issuing the command "$ aws iot describe-endpoint"
  • The port number is 8883 for the default AWS IoT broker
  • the TLS Handshake Timeout of 5000 is also pulled from the sample code

With these settings, and if you don't single step the call, the call succeeds; we can connect to our AWS instance.

rc = aws_iot_mqtt_connect( &connectionParams );
if (rc != NONE_ERROR) {
    Logger_LogError( "Call to 'aws_iot_mqtt_connect()' failed. Error: [%s]\n", 

                     AWSIoTErrorToString( rc ) );
    return;
}


With Mosquitto, you (can) setup the callbacks before you connect. And that's what I prefer to do, I'll need to figure out how and when to set those, which I assume may have something to do with the MQTTClient_t structure.


Callbacks? What Callbacks?

Ah Ha! Here's what's apparently going on.  The MQTTClient_t struct is passed into the "aws_iot_mqtt_init()" function.  According to the API docs:
This function provides a way to pass in an MQTT client implementation to the AWS IoT MQTT wrapper layer. This is done through function pointers to the interface functions.
So those function pointers in MQTTClient_t are not callbacks - they're the implementations.  Oh!  That's a bit different from the Mosquitto parts I've used.

I think I get it - most of the callbacks I implemented in Mosquitto were more out of curiosity than purpose. If the AWS SDK dumps the Connection Acknowledged callback, do I care?  Probably not.

Anyway, we're connected. So we'll keep adding in some AWS calls.

(#22) Amazon's Internet of Things Play 

Navigating the waters (avoiding the piranhas)


I've been making half-hearted attempts at some Cloud players in the IoT space. The two worth a blogpost are Microsoft's and Amazon's.  I'll hold off on Microsoft Azure for awhile, but suffice to say after a few days in URL-hell, after fighting their makefiles, I bailed.

I'll come back to Azure, but I couldn't see any reason to continue the fight. They had their expectations and those weren't mine.  Also 30 days of a "probably" free trial run on Azure wasn't anywhere close to sufficient. So I said "See ya' later -- maybe." to Azure.

Enter Amazon's AWS IoT play.  Well documented, free, an SDK, free, a port to the Raspberry Pi and free.  Did I mention it's free?  Twelve months of free?  And this is not free as in free beer, but free as in beer that costs $0.00.

While well documented, I bumped my head a few times.  Perhaps this post will help you avoid whacking your head on the same obstacles.

Recapping my setup

  1. I'm predominately a consumer of IoT data. I'm not a maker -- I produce IoT events (as MQTT messages) under duress. I only create events because I need them to satisfy my needs for consuming them.
  2. As a consumer, I'll probably take a number of shortcuts (aka hacks) in these posts on AWS IoT. Again, I'm trying to get my equipment to send IoT events as quickly and as painlessly as possible.
  3. I'm developing on Ubuntu 14.04 64bit, and as of this post, it's April 2016.
  4. I develop typically in C, Java and Python for IoT projects

Getting Started

Like everyone else, start with Amazon's IoT Developer's Guide. But pay attention to these details (that I glossed over).

Do not install the AWS CLI Debian package, instead use PIP to get the latest:


$ sudo apt-get install python-pip
$ pip install awscli


Make sure the IoT commands are present by trying:


$ aws iot help

Next, make sure your user/role has the proper permissions. To get going quickly, I opened myself up completely and added the AWSIoTFullAccess policy. Use the AWS Console and IAM to attach a policy:



Follow the developer guide to create a thing. Note that the DG glosses over the Thing's attributes.  Reading ahead, it looks like these attributes (three and at most three?) associated with the Thing are additonal identifiers of the Thing itself.

For example, three attributes could be: the vendor, the version and the serial number of the Thing.  The documentation lists this as an example:


$ aws iot describe-thing --thing-name "MyDevice3"
{
    "thingName": " MyDevice3",
    "defaultClientId": "MyDevice3",
    "attributes": {
        "Manufacturer": "Amazon",
        "Type": "IoT Device A",
        "Serial Number": "10293847562912"
     }

}
So, for my Weather Station Thing I could do something like:

$ aws iot describe-thing --thing-name "WS2308"
{
    "thingName": "WS2308",
    "defaultClientId": "WS2308",
    "attributes": {
        "Manufacturer": "LaCrosse",
        "Type": "WS2308-AL",
        "Serial Number": "00000001"
     }
} 


But setting the Thing Attributes aren't necessary to make progress. So we move on to creating the X.509 Certificates.

AWS IoT X.509 Cert Creation

I hit a couple  of bumps in the documentation, but probably because of my gross unfamiliarity with certificates.  Said another way, you're probably smarter and won't have the same issues.

Nota Bene #1 would be to choose more descriptive certificate file names  in the first create command. The DG shows this:

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


 
Three files will be created:
1. cert.pem - which will be referred to as the "device certificate" by other documentation and the sample code.  Name the file "cert.pem" if you wish, or name it "myThingCert.pem".
2. The public key cert, as of this point in my exploration, goes unused.
3. The private key cert is properly named, privateKey.pem, and you'll need this one later

Don't Forget the Root Cert

Finally note that we'll need a third Certificate to make things work and it's not created like the other two. It's downloaded.  And it's a short, small mention in the guide so don't miss it.  It's in this section of the Developer Guide

First paragraph:
MQTT clients require a root CA certificate to authenticate with AWS IoT. Download the root CA certificate file from root certificate.

Download it now, call it awsIoTRootCert.pem or something equally descriptive.
 
The AWS IoT SDK Sample code will suck in the complete path and filenames for these three certs so plunk them someplace useful.

Make a Note of the Certificate ARN

When the create-keys-and-certificate command completes, there will be some output similar to:

{
    "certificateArn": "arn:aws:iot:us-east-1:634:cert/cd----x-x-x-x-x-x--x-x-x8d153d02801c", 
    "certificatePem": "-----BEGIN CERTIFICATE----- 

 
Make a note of the certificate ARN (shown in red) as you'll need it later.

Don't change the Version on the Policy

Several steps later, you're asked to create a policy file. This JSON formatted file  as a Version attribute. Don't change it. It needs to be set to "2012-10-17" or things won't work.


$ pconroy@pconroy-VirtualBox:~/Dropbox/Amazon/IoT$ more policy.json {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action":["iot:*"],
        "Resource": ["*"]
    }]
 


MQTT.fx

The Developer's Guide uses a GUI java based tool called MQTT.fx to test the MQTT connectivity.  I tried the Debian based package first and it wouldn't start, so I downloaded the JAR file and ran it that way.

When it comes to the step of entering the full Certificate paths and filenames, take note that MQTT.fx again uses different terms.



The root certificate filename goes in the "CA File" text box.  The device certificate filename (called 'cert.pem' in the DG) goes in the "Client Certificate File" text box and the private key cert goes in the Client Key File text box.  The fourth text box, Client Key Password, remains blank.

MQTT.fx - Did you get a Connection Error?

I did. I kept getting this one:


2016-03-30 15:21:19,130 ERROR --- MqttFX ClientModel             : Error when connecting
org.eclipse.paho.client.mqttv3.MqttException: Connection lost
       at org.eclipse.paho.client.mqttv3.internal.CommsReceiver.run(CommsReceiver.java:146) ~[org.eclipse.paho.client.mqttv3-1.0.2.jar:?]
       at java.lang.Thread.run(Thread.java:745) [?:1.8.0_77]
Caused by: java.io.EOFException
       at java.io.DataInputStream.readByte(DataInputStream.java:267) ~[?:1.8.0_77]
       at org.eclipse.paho.client.mqttv3.internal.wire.MqttInputStream.readMqttWireMessage(MqttInputStream.java:65) ~[org.eclipse.paho.client.mqttv3-1.0.2.jar:?]
       at org.eclipse.paho.client.mqttv3.internal.CommsReceiver.run(CommsReceiver.java:107) ~[org.eclipse.paho.client.mqttv3-1.0.2.jar:?]
       ... 1 more
2016-03-30 15:21:19,154  INFO --- ScriptsController              : Clear console.
2016-03-30 15:21:19,156 ERROR --- BrokerConnectService           : MqttException: Connection lost


A little Googling showed that I wasn't alone; others were getting the same error. But it wasn't common, it seemed to be rare.  A there wasn't much in the way of guidance on making the error go away.  



After poking at it for a couple of hours, I started over from the Certificate creation steps - and redid everything.  Tried it again and -- it worked. No errors.


I have no cure just a suggestion that if you too hit a Connection Error, just back up and recreate all of the certs, policies and attachments.

 

AWS IoT SDK for C

That's about all it took for me to work through the Quickstart Section of the Developer's Guide.  At this point, I decided to start in on their SDK and port my existing Home Automation Devices over to using their SDK.  That's the topic of my next post.