Quick Guide: Integrating real time data to websites using MQTT and WebSockets

High Level

This Guide, while extremely contrived, is actually a fairly good representation of a typical IoT project. There are two sensors in the field, they represent the cat and a scratching post. The idea is to see how far the cat is from it's scratching post and to upload this distance to the cloud. Once in the cloud, we want to show this data to the cat owner in a website in realtime.

There are Four ingredients required to make this work

  • ESP32 (BLE anchor + web connectivity)
  • Tracking modules (nRF52 + UWB DecaWave1000) x 2
  • Golang backend
  • AWS MQTT Broker

There is a lot going on here, to keep things simple, error checking is kept to a minimum - this is not a finished or usable project, but it does show what's required to at least get a PoC up and running, filling in the blanks to get to the finished product is as always both the easy and difficult part.


aws

Youtube demo:

UWB

To start, lets talk about what UWB communication is. It's just a communication scheme to send data across the air. The on-chip machinery that is required to process UWB signals lends itself very well to accurately timing both when a UWB message was sent, and also when an UWB was received. Given this, it's possible to do something called "two-way ranging" (TWR). Using TWR, we can find the distance between two senors through a process shown below.

twr

In short, the initiator sends a message to the Responder. Once the Responder receives this message, it delays for a short period of time, then replies to the Initiator. The Initiator makes a note of when it received the response and calculates the total-round trip time, which will be equal to Treply + 2(Ttof), where tof is simply the amount of time it takes the RF message to travel between the Initiator and the Responder (or the speed of light through air).

You might wonder how it's possible to time the reply to such an accuracy - the answer is that the DecaWave1000 module allows us to queue up a message to be sent some time in the future . This in essence is what is required to get this TWR trick to work. The accuracy of this process is about 10cm, each nanosecond of error adds about 30cm of error (how much light travels in 1ns).

I am using an off the shelf board that has both an nRF52xx on its as well as a DecaWave1000. I would NOT recommend that you try to write the driver for the DecaWave1000 part from scratch since it requires intimate knowledge of the chip. DecaWave provides a pretty good API, and from there it's just plug and play to integrate it into your code. See here https://www.decawave.com/software", of interest to us is:

  • deca_device.c
  • deca_device_api.h
  • deca_param_init.c
  • deca_param_types.h
  • deca_regs.h
  • deca_types.h
  • deca_versions.h
Have a look at the "DW1000 Application Programming Interface with STM32F42x Application Examples" - while they are meant to be used for the STM platform it's trivial to export them to other CPUs. This requires us to write some very few CPU specific APIs (spiwrite, spiread, etc) - for the NRF52XX platforms you can have a look at spi_core.c to see how I ported it over. To implement the actual TWR, have a look at this example as provided by DecaWave: DecaWave TWR example

BLE

Once the two devices figure out the distance between them (representing the distance between he cat and the scratching post), The sensor on the cat will use BLE to push this distance to the ESP32, the ESP32 will then push the distance to the AWS IoT core using MQTT. Our ESP32 will have one GATT service, which has two characteristics.

  • Current Time characteristic
  • Upload Distance characteristic

The current time characteristic is used so the two devices in the field can connect to the ESP32 and read the time, in units of seconds past Unix epoch. The ESP32 gets the time from an NTP server. This can be used for debugging. One of the devices (the one attached to our cat) will use the second characteristic to write the distance it resolved to, also it will include the time at which the distance was measured. The ESP32 will then use MQTT to send the data to the AWS IoT Core.

The code for the NRF code for the BLE/UWB can be found here NRF Tutorial Code

ESP32 MQTT

For this project, we are using the AWS IoT MQTT broker. We need to set up the ESP32 and also the golang webserver to be able to connect and authenticate with the Broker. Amazon provides a pretty easy way to set up an ESP32 to work with the AWS cloud. Clone: https://github.com/aws/amazon-freertos For the freeRTOS AWS tools,

  1. Install the AWS CLI client Install AWS CLI
  2. Configure the AWS cli ( $ aws config ), this requires your AWS key and location. Make sure you get your location set up correctly as this is important (ie, us-west-2, etc)
  3. install boto3, ( $pip install boto3 )
  4. go to freertos/tools/aws_config_quick_start, open configure.json, update the "thing_name" to something new, this must be unique. This creates a new "thing" in the IoT core. We will use this "things" certificates to identify our ESP32 to the IoT core and to publish to the AWS IoT Core MQTT Broker.
  5. Configure the WiFi security, password and ssid
  6. run the script to generate the thing and the certs, ( $python3 SetupAWS.py setup )
  7. After running the script, the thing should have been created in the IoT core. For example, my thing name is "cat_locator"

For more details on the Script we just ran, check out here: AWS Getting Started w/ ESP32

Validate the output of the script we just ran, should be something like this:


setup script


Also, we can have a look in the AWS IoT core to make sure our device exists


aws

Technically all we care about is the certs and the policy attached to the certs, we don't really need to register our thing with the IoT core, the fact that we did this is irrelevant. Later in the tutorial we will go over how to generate the certs and policy without using this script. The reason we used this script is that it's quite simple and does a lot of the leg work for us

The script will have generated the file aws_clientcredential_keys.h (freertos/demos/include/aws_clientcredential_keys.h), verify the RSA private key and certificate is in this file. Likewise, have a quick look and make sure aws_clientcredential.h makes sense. This file contains our IoT endpoint, broker port, etc.

Now that we have this set up, we need to incorporate these keys into the ESP32. A good place to start is the default esp-idf MQTT example, found here: esp-idf/examples/protocols/mqtt/ssl/main/app_main.c, we will make some changes to this to be able to talk with AWS. Mainly updating mqtt_cfg to point to the AWS broker and including the cert + key.

aws

All the values used to set up the connection come from aws_clientcredential.h and aws_clientcredential_keys.h, the example should run and connect to the broker. Make sure there are no errors or warnings in the console logs

As a further test, we can use the built in AWS IoT MQTT client to test the connection. Modify the MQTT code to periodically publish to some topic, I picked /topic/cat_location, then navigate to the MQTT test client in the AWS IoT Core and subscribe to that topic, as such:

aws
aws

At this point, we have both the ESP32 and the two infield sensors working, next we will set up the golang webserver


The code for the ESP32 can be found here ESP32 Tutorial Code

Golang MQTT

The first thing we need to do is to set up the golang MQTT client, Eclipses Paho client can be found on github, See Here for a pretty detailed explanation for setting up the Golang AWS MQTT client. The code for the golang MQTT client is show below:

aws

There are a few things to note above, first, we need to get get a rootCA from AWS, and also we need to generate a private/public key pair AND register these wth AWS and tie them in to a policy

First, get the root cert:
$ curl -o ~/greengrass-v2-certs/AmazonRootCA1.pem https://www.amazontrust.com/repository/AmazonRootCA1.pem

Next, we need to generate the certs,
$ aws iot create-keys-and-certificate --set-as-active --certificate-pem-outfile cert.crt --private-key-outfile private.key --public-key-outfile public.key --region us-west-2

Take note of the ARN value for the certificate, we will need it later. Also make sure that the region is correct for you. Next, generate the IoT policy. The following policy is too broad for real life use (it gives access to all IoT features) - but for this simple project that's fine. You only need to generate the IoT policy once. It can be reused.
$ aws iot create-policy --policy-name MQTT_POLICY --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action": "iot:*","Resource":"*"}]}'

Now attach the IoT policy to the certificate ARN we got in the previous step.
$ aws iot attach-policy --policy-name MQTT_POLICY --target < YOUR CERTICATE ARN >

Once this is set up, go ahead and fire up the MQTT client on the golang side and publish to a topic. Once again, use the AWS MQTT Test client to verify connectivity and proper authentication.

Now that we got the permissions for the MQTT client setup, we will do a little leg work and implement the application logic. Basically, our ESP32 device publishes an MQTT message to the AWS core, what we want to do is to subscribe to this topic in our golang webserver and redirect it to a browser through websockets.

Golang MQTT

The golang webserver is trivial for this example, after executing a template to load the HTML, we will upgrade the HTTP connection to a websocket. We will use github.com/gorilla/websocket to hijack and upgrade the HTTP connection to a websocket. Once we have upgraded the connection, we will listen to the MQTT broker, and once a we get a message, push it to the webserver.

aws

HTML

Finally, we will set up the website itself. This is also fairly trivial. We will open a websocket and listen on it. The JSON objects that the websocket received are translated into the location of our cat on the screen using some simple javascript style manipulation

The code for the Golang website and go MQTT backend can be found here Golang/Webserver Tutorial Code
ble

Sources Summary:

ESP32 FW
NRF FW
Golang/Webserver code