AWS IoT with MQTT: A Code Review

Pawel Czerwinski

In a previous article, we walked through a step by step process of how to get started with Amazon’s AWS IoT Core managed cloud service. To do this, we used the lightweight MQTT messaging protocol and a NetBurner development kit as the IoT device. While we covered all of the base requirements from start to finish, we also glossed over an awful lot concerning the actual application running on the NetBurner module. In this article, we’re going to revisit the program, crack it open, and take a peek at how it ticks. This walk-through will give you the jump-start you need to design even more robust applications around these core functions.

Powered by AWS Cloud Computing - IoT Core
Read more about the IoT Core Cloud Service here

IoT Dev Kit Featured in this Article

Netburner ARM Cortex M7 embedded Development Kit for IoT product development and industrial automation.

Or, learn more about NetBurner IoT.

First, we’re going to look at the functionality of the overall application. Next, we are going to review the source code and get a feel for where everything lives. Finally, we will get up close and personal with the individual bits that do the dirty work. We’ve included a substantial part of the application in the article below, but if you want the truly immersive experience, you can find the GitHub repo for the project here.

Program Overview

Our application is very straight forward, and implements four distinct pieces of functionality:

  1. Initial setup
  2. Publish an MQTT message ever 5 seconds
  3. Subscribe to an MQTT topic to receive incoming messages
  4. Provide a web interface to view the last message sent and received

We expect the messages to be published and received in the JSON format and use the ParsedJsonDataSet class extensively in processing the MQTT payload data.

Source Code Review

AWS IoT Core with NetBurner IoT Device and MQTT Github Repo
Figure 1: The Project Files

Our project contains several source files, the AWS SDK, and an html folder that includes the index.html we use in our web interface. In this article, we will concern ourselves mostly with the six .cpp files directly in the src folder (shown above in Figure 1), their associated header files, and a configuration header file. The following is a brief description of each of these:

main.cpp – This is the starting point of the application. It defines the application name, AWS IoT Core Base, and provides the entry point for our program with UserMain(). More on this bit later.

post-record-data.cpp – This file contains the functions that initialize the AWS SDK, and handles processing both incoming and outgoing MQTT messages.

network_netburner.cpp – This file contains the functions that are required to implement the networking interface for the AWS SDK API and ties it to our networking stack. These are all exclusively related to the TLS network connection.

timer_netburner.cpp – This file contains additional functions that are required by the AWS IoT Core MQTT client, and are related to the timer functionality that is used to manage the MQTT connections.

record-data.cpp – This contains a small function that serializes the JSON data used in the MQTT messages.

html-control.cpp – This file contains two functions used to build the HTML page that displays the most recently sent and received MQTT messages, as shown below.

aws_iot_config.h – This file has all of the config constant values that we need to connect to our service and publish/receive messages. As mentioned in the previous article, you will want to change both AWS_IOT_MQTT_HOST and AWS_IOT_MQTT_CLIENT_ID to values that are specific to your account and environment.

Web interface displaying last received and sent message via AWS IoT Core, MQTT and Netburner IoT Device
Figure 2: Web interface displaying last received and sent message

Initial Setup

Let’s turn our attention to the entry point of our application, UserMain(), found in main.cpp. Inside UserMain(), you will see calls to the following functions, more or less in the given order, before we hit our main while() loop:

init() – This function sets up the network stack for the application, starts the configuration server, gives UserMain() the correct task priority, and sets up a few debug details. If you happen to forget this in your application, you will know very quickly.

StartHttp() – This kick-starts the webserver on the device. It couldn’t be easier.

InitializeAWSSDK() – This does pretty much exactly what you would expect. It sets up the AWS SDK and takes care of the initial MQTT setup. We’ll dive into this a bit more below.

After everything is initialized, we will enter our main while loop, where you will see the following bit of code:

    while (1)
    {
        // Post our record data
        PostRecordData();
        // Wait 5 seconds before doing it again
        OSTimeDly(TICKS_PER_SECOND * 5);
    }

In true embedded systems fashion, we will continue to run our while() loop indefinitely. Every five seconds we will make a call to PostRecordData(), which will post a message to our subscribed MQTT topic, defined as gTopicNameSend in post-record-data.cpp.

Initializing the AWS IoT Core SDK for MQTT

As mentioned above, initializing the AWS SDK for IoT Core services takes place in InitializeAWSSDK(), found in post-record-data.cpp.

bool InitializeAWSSDK()
{
    IoT_Error_t rc = FAILURE;
    IoT_Client_Init_Params mqttInitParams = iotClientInitParamsDefault;
    IoT_Client_Connect_Params connectParams = iotClientConnectParamsDefault;

    iprintf("\nAWS IoT SDK Version %d.%d.%d-%s\n", VERSION_MAJOR, VERSION_MINOR,
    VERSION_PATCH, VERSION_TAG);

    // Set required MQTT parameters
    mqttInitParams.enableAutoReconnect = false;
    mqttInitParams.pHostURL = AWS_IOT_MQTT_HOST;
    mqttInitParams.port = AWS_IOT_MQTT_PORT;
    mqttInitParams.pRootCALocation = "";
    mqttInitParams.pDeviceCertLocation = (char *) certificate;
    mqttInitParams.pDevicePrivateKeyLocation = (char *) privatekey;
    mqttInitParams.mqttCommandTimeout_ms = 30000;
    mqttInitParams.tlsHandshakeTimeout_ms = 10000;
    mqttInitParams.isSSLHostnameVerify = true;
    mqttInitParams.disconnectHandler = nullptr;
    mqttInitParams.disconnectHandlerData = nullptr;

    rc = aws_iot_mqtt_init(&client, &mqttInitParams);
    if (SUCCESS != rc)
    {
        iprintf("aws_iot_mqtt_init returned error : %d ", rc);
        return false;
    }

    connectParams.keepAliveIntervalInSec = 600;
    connectParams.isCleanSession = true;
    connectParams.MQTTVersion = MQTT_3_1_1;
    connectParams.pClientID = AWS_IOT_MQTT_CLIENT_ID;
    connectParams.clientIDLen = (uint16_t) strlen(AWS_IOT_MQTT_CLIENT_ID);
    connectParams.isWillMsgPresent = false;

    iprintf("Connecting...\n");
    rc = aws_iot_mqtt_connect(&client, &connectParams);
    if (SUCCESS != rc)
    {
        iprintf("Error(%d) connecting to %s:%d\n", rc, mqttInitParams.pHostURL, mqttInitParams.port);
        return false;
    }

    rc = aws_iot_mqtt_autoreconnect_set_status(&client, true);
    if (SUCCESS != rc)
    {
        iprintf("Unable to set Auto Reconnect to true - %d\n", rc);
        return false;
    }

    iprintf("Subscribing to topic: %s, %d\n", gTopicNameRec, strlen(gTopicNameRec));
    rc = aws_iot_mqtt_subscribe(&client, (char *) gTopicNameRec, strlen(gTopicNameRec), QOS1, IoTSubscribeCallbackHandler, nullptr);

    if (SUCCESS != rc)
    {
        iprintf("Error subscribing : %d\n", rc);
        return false;
    }

    return true;
}

Near the top of the function, you will see the parameters getting set for the call to aws_iot_mqtt_init(). You should be okay with sticking to the values we’ve provided. However, for the exceptionally curious, you can find out more about each one from the official AWS API documentation, here.

Assuming the MQTT initialization function is successful, we will then set up our connection parameters and pass them into the function, aws_iot_mqtt_connet(). As you might guess, establishes a connection to the AWS IoT Core service. Again, you should be okay with the values we set, but you can find more information on what these represent here. If we’re able to successfully set up a connection, we follow it up with a call to aws_iot_mqtt_autoreconnect_set_status() which includes a parameter of true. This will make sure that we automatically reconnect if our connection fails for some reason.

Finally, we subscribe to our topic with a call to aws_iot_mqtt_subscribe(). The topic that we will subscribe to is gTopicNameRec, which is defined at the top of post-record-data.cpp. We also pass a pointer to the function that is called when we receive a message, IoTSubscribeCallbackHandler().

Publishing MQTT Data

Now that we’ve established a connection to the AWS IoT Core service, we are ready to start publishing messages using the MQTT protocol. As we mentioned before, we will post a new message once every five seconds from our UserMain() function with a call to PostRecordData(), which is defined in post-record-data.cpp.

void PostRecordData()
{
    char buffer[200] = { 0 };
    CreateOutMessage(gJsonOut);
    gJsonOut.PrintObjectToBuffer(buffer, 199, false);

    IoT_Publish_Message_Params paramsQOS1;
    paramsQOS1.qos = QOS1;
    paramsQOS1.payload = (void *) buffer;
    paramsQOS1.payloadLen = strlen(buffer);
    paramsQOS1.isRetained = 0;

    IoT_Error_t rc = aws_iot_mqtt_publish(&client, gTopicNameSend, strlen(gTopicNameSend), &paramsQOS1);
    if (SUCCESS != rc)
    {
        iprintf("Error publishing : %d\n", rc);
    }
    else
    {
        iprintf("Publish success:\r\n");
        gJsonOut.PrintObject(true);
        iprintf("\r\n");
    }
}

The first thing you’ll notice we do is call CreateOutMessage(). This builds our MQTT payload and stores it in gJsonOut, which is a ParsedJsonDataSet object. We save this globally so that it can be referenced and displayed via a webpage served up by our module. The contents of the record are a timestamp of when the message was created and a static id that is incremented whenever the function is called. The record is defined by the class PostRecord and is serialized into gJsonOut along with the device id, AWS_IOT_MQTT_CLIENT_ID.

With our payload correctly structured, we dump it into a buffer before setting up our IoT publish parameters.

Receiving MQTT Messages

As mentioned before, our call to aws_iot_mqtt_subscribe() during our initial setup subscribed us to the topic, gTopicNameRec. One of the parameters to that function, IoTSubscribeCallbackHandler, provided the handler to the function that will be used to process incoming messages.

void IoTSubscribeCallbackHandler(AWS_IoT_Client *pClient, char *topicName, uint16_t topicNameLen, IoT_Publish_Message_Params *params, void *pData)
{
    iprintf("Received Message:\r\n");
    ParsedJsonDataSet jsonIn = ParsedJsonDataSet((char *) params->payload, (int) params->payloadLen);
    jsonIn.PrintObject(true);
    iprintf("\r\n");

    jsonIn.PrintObjectToBuffer( recJson, 256, true);
}

We know what the signature of the function should be because Amazon is kind enough to give us a pretty good breakdown in their API documentation. The function itself is very short, as we simply take the payload data from the MQTT message, and use it to construct a ParsedJsonDataSet object. We then stash the contents of that object into a buffer so that we can serve it up on the webpage and show what the last message received was.

The Web Interface

We’ve taken advantage of NetBurner’s ability to generate dynamic web content to display the most recently received and published MQTT messages. To do this, we defined two functions in html-control.cpp that will print the data to the webpage before it served to the requesting browser from the device. These are PrintOutData() and PrintInData().

Specially formatted comments in index.html trigger these functions, and they have the structure, . This makes it very easy to display any sort of application-level data through the web interface. We have extensive documentation on this system and include several fully functional examples with our NNDK.

Implementing the AWS SDK Dependencies

Of course, there has to be a way for the AWS SDK to tie into the NetBurner platforms and networking stack. There are two interfaces provided by the SDK that we use here: the network interface and the timer interface. As mentioned above, the functions for these interfaces are defined in network_netburner.cpp and timer_netburner.cpp respectively.

The network interface provides all of the TLS functionality for initiating and closing secure connections, as well as reading and writing data over those connections. The timer interface provides the timeout functionality required to keep the MQTT connections alive and kicking.

The functions for both of these interfaces were required before we could connect to the AWS IoT Core service and start sending and receiving MQTT messages. If you followed the instructions in the previous article on generating and compiling the certificate and private key, there shouldn’t be any need to modify the functions implemented here. That said, feel free to dig in and let us know if you have any specific questions or comments.

Closing

We’ve provided a solid foundation here, but this is truly just the beginning of where this particular project can go. In the coming months, we will start to explore incorporating other AWS services into our IoT application.

Don’t wait for that before you jump into your own projects, though! We’d love to hear what you’re up to, and how you’re using Amazon’s AWS IoT Core service or our development kits. Feel free to email us directly at sales@netburner.com or leave a comment below.

Also, if you want to learn more about IoT architectures and better understand how IoT devices, protocols, and cloud services all work together, click on through to our article on just that!

Cover photo credit: Pawel Czerwinski

Share this post

Subscribe to our Newsletter

Get monthly updates from our Learn Blog with the latest in IoT and Embedded technology news, trends, tutorial and best practices. Or just opt in for product change notifications.

Leave a Reply
Click to access the login or register cheese