Tomas Dosoudil

An enthusiastic software engineer


Connecting RPi Pico W to AWS IoT Core

Published April 11, 2023

In this post I will show you how to connect your RPi Pico W to an mqtt broker and send temperature to the cloud. We will configure AWS IoT Core as a broker.

Creating a new AWS IoT Core Thing

The first thing is to create a thing ;-). I am assuming you already have an AWS account and therefore we will focus on setting up the IoT Core only.

Create a thing

You need to navigate to AWS IoT Core -> All devices -> Things and click on Create things button. Create a new thing in AWS and call it pico_w_simple with an unnamed shadow (classic)

Create a thing

Generate a new certificate

In order to communicate with AWS a device (RPi Pico W in our case) needs to have x509 certificate. This allows the device to connect to AWS IoT Core.

Create a new certificate

Policy

We will create a basic aws policy that will be later on attached to the certificate. The policy defines more granular actions a device can perform. We create a basic policy that allows all actions.

Create a new policy

Attach the policy

Attach previously created policy to the certificate.

Attach policy

Download all certificates and keys

This is the last step where we need to download at least private key and certificate. Once you close this window you won’t be able to download it anymore.

Download all keys and certificates

Keys conversion

Both private key and certificate are in a PEM format. We will need to convert them to DER format as this format is later on used in our code. We can use the following snippet.

#!/bin/bash

# Converts a certificate and private key downloaded from AWS 
# in PEM format into DER format.

openssl x509 -in certificate.pem.crt -out certificate.der -outform DER
openssl pkey -in private.pem.key -out private.der -outform DER

Pico W code

In order to connect your Pico to AWS IoT Core, you will need to use an mqtt client, previously converted key with certificate and main code.

As mqtt client you may use umqtt.simple from micropython-lib.

Files on your should look like this:

.
├── keys
│   ├── certificate.der
│   └── private.der
├── main.py
└── umqtt
    └── simple.py

Where the main.py file contains the following code:

import network
from machine import Pin
from umqtt.simple import MQTTClient
import time

# WIFI parameters
# Access point name
ACCESS_POINT = ""
# Access point password
PASSWORD = ""

# AWS endpoint parameters.
# Should be different for each device can be anything
CLIENT_ID = "pico_w_simple"

# AWS iot core endpoint
AWS_ENDPOINT = b'ai9pi313gw5mi-ats.iot.eu-central-1.amazonaws.com'

# MQTT channel name for publishing
PUBLISH_CHANNEL='$aws/rules/telemetry/' + CLIENT_ID

# Set LED 
LED = Pin("LED", machine.Pin.OUT)
LED.value(0)

def get_ssl_params():
    """ Get ssl parameters for MQTT"""
    keyfile = '/keys/private.der'
    with open(keyfile, 'rb') as f:
        key = f.read()
    certfile = "/keys/certificate.der"
    with open(certfile, 'rb') as f:
        cert = f.read()
    ssl_params = {'key': key,'cert': cert, 'server_side': False}
    return ssl_params

def wifi_connect(wlan):
    """Wait for connection"""
    print("WiFi connecting: ", end = "")
    while not wlan.isconnected():
        print(".", end = "")
        led_blink()
        led_blink()
        time.sleep_ms(1300)
        wlan.connect( ACCESS_POINT, PASSWORD )
    
    print(" connected")

def read_internal_temp_sensor():
    """Read internal temperature sensor of Pico W"""
    sensor_temp = machine.ADC(4)
    conversion_factor = 3.3 / (65535)
    reading = sensor_temp.read_u16() * conversion_factor
    temperature = 27 - (reading - 0.706)/0.001721
    return temperature

def led_blink():
    """Led blinking for a simple debug"""
    LED.value(1)
    time.sleep_ms(50)
    LED.value(0)
    time.sleep_ms(50)

# Setup WiFi connection.
wlan = network.WLAN( network.STA_IF )
wlan.active(True)
wifi_connect(wlan)

# Connect to AWS MQTT broker.
ssl_params = get_ssl_params()
mqtt = MQTTClient( CLIENT_ID, AWS_ENDPOINT, port = 8883, keepalive = 10000, ssl = True, ssl_params = ssl_params )
mqtt.connect()

while True:    
    # Publish temperature
    temperature = read_internal_temp_sensor()
    msg = b'{{"temp": {}}}'.format(temperature)
    mqtt.publish(topic = PUBLISH_CHANNEL, msg = msg , qos = 0)
    
    print(msg)
    led_blink()
    time.sleep_ms(2000)

Rules

At this stage your device is able to connect to AWS and send temperature. However there is nothing subscribed to these messages and they are not processed by any rule.

Let’s create a new rule under AWS IoT Core -> Message routing -> Rules. The rule is called telemetry in the above example and a new basic ingest topic name will be $aws/rules/telemetry.

Once you create it the settings should look like this:

Rule

SQL statement defines that we want keep all keys from the original message and add topic() as device_id and current timestamp as received_ad. The device_id will be pico_w_simple in our case because all message are published to $aws/rules/telemetry/pico_w_simple

SELECT *, topic() as device_id, timestamp() AS received_at

The current rule is still doing nothing as we have not defined any action. You can add a new action by clicking on Edit button in the rule. The simple action might be logging all incoming message to CloudWatch Logs.

Rule action

If everything is configure correctly and you run your Pico W you should start getting messages to iot-core-telemetry log group.

A message should look like this:

{
  "temp": 27.51254,
  "device_id": "pico_w_simple",
  "received_at": 1679781014810
}

And that’s it.