Using AWS IoT with Congress

There’s a lot of different cloud IoT providers out there and Amazon is one of the biggest (if not the biggest). They started out relatively simple with EC2 and have added new services and features at a blistering pace the last few years AWS IoT is one of them.

They have a fairly simple model: The devices are referred to as things (obviously), data from devices is stored in thing shadows and that’s about it. Once the data is put into the AWS IoT backend you can process it with AWS Lambda or any other AWS service that fits your bill. More information can be found in the AWS IoT documentation.

Congress uses the MQTT queue with a client certificate and updates the thing shadows based on the device EUI. Let’s start by going into the AWS console and create a new client certificate. Click on the Secure menu item, the on the Create button in the right hand corner. Select the One-click certificate button to create a new certificate and download the certificate file and the private key files. We’ll use those to configure the output. Remember to click on the Activate button to enable the certificate.

Next we have to attach a policy to the certificate. We’ll just make it simple and allow anything for now. The policy looks like this:

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

If you just have a few devices that you’ll be using you can create things for your devices in Congress manually but if you have more than a few it quickly becomes tedious to keep the list of things in sync with the Congress devices. Let’s automate it with some Go code! This snippet will sync devices in Congress with things in AWS using the Lassie client library:

func createAwsThings(client *lassie.Client) {
	list, err := client.Devices(appEUI)
	if err != nil {
		fmt.Println("Unable to load devices: ", err)
		os.Exit(2)
	}

	svc := iot.New(session.New(), aws.NewConfig())

	if _, err = svc.DescribeThingType(&iot.DescribeThingTypeInput{
        ThingTypeName: aws.String("CongressDevice")}); err != nil {
		if _, err := svc.CreateThingType(&iot.CreateThingTypeInput{
			ThingTypeName: aws.String("CongressDevice"),
		}); err != nil {
			fmt.Println("Unable to create thing type. Does it already exist? error = ", err)
		}

	}

	for _, v := range list {
		fmt.Println("Creating thing for device ", v.EUI)

		if _, err := svc.DescribeThing(&iot.DescribeThingInput{
            ThingName: aws.String(v.EUI)}); err != nil {
			if _, err := svc.CreateThing(&iot.CreateThingInput{
				ThingName:     aws.String(v.EUI),
				ThingTypeName: aws.String("CongressDevice"),
			}); err != nil {
				fmt.Println("Unable to create thing. error = ", err)
			} else {
				fmt.Println("Thing created")
			}
		} else {
			fmt.Printf("Got result: %+v, ignoring thing\n", err)
		}
	}
}

Similarly we can create a new output:

func createAwsOutput(client *lassie.Client) {
	buf, err := ioutil.ReadFile(certFile)
	if err != nil {
		fmt.Println("Unable to read certificate file: ", err)
		os.Exit(3)
	}
	certFileContents := string(buf)
	buf, err = ioutil.ReadFile(privKeyFile)
	if err != nil {
		fmt.Println("Unable to read private key file: ", err)
		os.Exit(4)
	}
	privKeyFileContents := string(buf)
	outputConfig := lassie.AWSIoTConfig{
		Endpoint:          awsEndpoint,
		ClientCertificate: certFileContents,
		PrivateKey:        privKeyFileContents,
	}

	if _, err := client.CreateOutput(appEUI, &outputConfig); err != nil {
		fmt.Println("Unable to create output: ", err)
		os.Exit(5)
	}
}

You can find the source code for this on GitHub. Run go get github.com/telenordigital/awsiot-lora-things to retrieve the latest version.

Let’s make a simple LoPy device that sends a message every time the button on the breakout board is clicked. Create a device in Congress, grab the LoPy provisioning code and modify it to listen on a button push:

from network import LoRa
import socket
import time
import binascii
import pycom
import  machine
from machine import Pin

def flash_led(col):
	pycom.rgbled(col)
	time.sleep(0.1)
	pycom.rgbled(0x000000)
	time.sleep(0.1)

# Turn off the heartbeat blink and the LED
pycom.heartbeat(False)
pycom.rgbled(0x000000)

p_in = Pin('G17', mode=Pin.IN, pull=Pin.PULL_UP)

# Initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN)

# Device provisioning. Use either OTAA or ABP
OTAA_DEVICE = True 

#print('Running OTAA join')

# OTAA parameters
dev_eui = bytes([ 0x00, 0x09, 0x09, 0x00, 0x00, 0x01, 0x50, 0x01])
app_eui = bytes([ 0x00, 0x09, 0x09, 0x00, 0x00, 0x00, 0x03, 0x37])
app_key = bytes([ 0xff, 0x90, 0x47, 0x66, 0x8d, 0x1a, 0x30, 0xed, 0xc7, 0x27, 0x01, 0xba, 0xba, 0x96, 0x69, 0x47])


lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0)

# wait until the module has joined the network
while not lora.has_joined():
	flash_led(0xff0000)

for i in range(1, 2):
	flash_led(0x0000ff)

s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)

# set the LoRaWAN data rate
s.setsockopt(socket.SOL_LORA, socket.SO_DR, 5)

# create a raw LoRa socket
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)


pushes = 0

while True:
	push = p_in()
	if push == 0:
		pushes = pushes + 1
		print('Sending button push (count = ', pushes, ')')
		flash_led(0xffff00)
		s.setblocking(True)
		s.send(bytes([pushes]))
		flash_led(0x00ffff)

	time.sleep(1)

The source code for the example can be found in the same GitHub repository as the thing updater source.

I’m using the extension board so the button is connected to G17. If you use another board (or the bare bones LoPy) your button might be connected to another pin.

You should now see the button counter increase in the Congress console:

Data from device

If we now run the thing updater we’ll see our new LoPy device in the AWS console and the thing shadow is updated whenever we click on the button on the LoPy:

Thing shadow

That’s it!