Help: Connecting Raspberry Pi Pico W to AWS IoT Core using Micropython

0

I'm trying to connect my Raspberry Pi Pico W to AWS IoT Core for sending sensor data. I've installed the umqtt.simple library and followed some online guides, but I'm encountering errors when handling the certificates and establishing the connection.

I'm aiming for a secure connection and would appreciate any help with code snippets or best practices for achieving this.

Specifically, I'm unsure about:

How to properly read and use the downloaded certificate and private key files. The syntax for establishing a connection using umqtt.simple with secure communication enabled. Thanks in advance for any assistance!

This is the code I used establishing the connection.

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

# AWS IoT Core - RPi Pico W Demo

# Required imports
import time
import machine
import network
import ujson
from umqtt.simple import MQTTClient

[code][code][code]
###############################################################################
### START CODE MODIFICATION ###################################################
###############################################################################

# Wifi Name / SSID
SSID = b'<your wifi network name>'
# Wifi Password
PASS = b'<your wifi password>'

# AWS ThingName is used for the Client ID (Best Practice). Example: RaspberryPiPicoW
CLIENT_ID = b'RaspberryPiPicoW'
# AWS Endpoint (Refer to "Creating a Thing in AWS IoT Core" step 13)
AWS_ENDPOINT = b'<your IoT Endpoint value here>'

###############################################################################
### END CODE MODIFICATION #####################################################
###############################################################################


# AWS IoT Core publish topic
PUB_TOPIC = b'/' + CLIENT_ID + '/temperature'
# AWS IoT Core subscribe  topic
SUB_TOPIC = b'/' + CLIENT_ID + '/light'


# Reading Thing Private Key and Certificate into variables for later use
with open('/certs/key.der', 'rb') as f:
    DEV_KEY = f.read()
# Thing Certificate
with open('/certs/cert.der', 'rb') as f:
    DEV_CRT = f.read()


# Define light (Onboard Green LED) and set its default state to off
light = machine.Pin("LED", machine.Pin.OUT)
light.off()


# Wifi Connection Setup
def wifi_connect():
    print('Connecting to wifi...')
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASS)
    while wlan.isconnected() == False:
        light.on()
        print('Waiting for connection...')
        time.sleep(0.5)
        light.off()
        time.sleep(0.5)
    print('Connection details: %s' % str(wlan.ifconfig()))


# Callback function for all subscriptions
def mqtt_subscribe_callback(topic, msg):
    print("Received topic: %s message: %s" % (topic, msg))
    if topic == SUB_TOPIC:
        mesg = ujson.loads(msg)
        if 'state' in mesg.keys():
            if mesg['state'] == 'on' or mesg['state'] == 'ON' or mesg['state'] == 'On':
                light.on()
                print('Light is ON')
            else:
                light.off()
                print('Light is OFF')


# Read current temperature from RP2040 embeded sensor
def get_rpi_temperature():
    sensor = machine.ADC(4)
    voltage = sensor.read_u16() * (3.3 / 65535)
    temperature = 27 - (voltage - 0.706) / 0.001721
    return temperature


# Connect to wifi
wifi_connect()

# Set AWS IoT Core connection details
mqtt = MQTTClient(
    client_id=CLIENT_ID,
    server=AWS_ENDPOINT,
    port=8883,
    keepalive=5000,
    ssl=True,
    ssl_params={'key':DEV_KEY, 'cert':DEV_CRT, 'server_side':False})

# Establish connection to AWS IoT Core
mqtt.connect()

# Set callback for subscriptions
mqtt.set_callback(mqtt_subscribe_callback)

# Subscribe to topic
mqtt.subscribe(SUB_TOPIC)


# Main loop - with 5 sec delay
while True:
    # Publisg the temperature
    message = b'{"temperature":%s, "temperature_unit":"Degrees Celsius"}' % get_rpi_temperature()
    print('Publishing topic %s message %s' % (PUB_TOPIC, message))
    # QoS Note: 0=Sent zero or more times, 1=Sent at least one, wait for PUBACK
    # See https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html
    mqtt.publish(topic=PUB_TOPIC, msg=message, qos=0)

    # Check subscriptions for message
    mqtt.check_msg()
    time.sleep(5)
 
3 Answers
0

I would like to add to this question based on my experience with this same issue. This topic not easy to find up-to-date solutions for, when it comes to the problem of using the most recent MicroPython umqtt library (1.4.0) with AWS IoT Core. Most YouTube, Raspberry Pi community guidance/examples are for an older version of umqtt. I hope this helps others who are trying to google for a solution.

Context I have a Raspberry Pi Pico W connected to a development board (BCRobot Pico-Irrigation), which acts as an automated irrigation system. I was trying to use IoT Core to communicate with this system and receive sensor data using MQTT.

MicroPython Version: v1.23.0 MQTT Library: v1.4.0

Issue Using the above configuration for SSLContext from muimu and the AWS community post linked by Max, I was still unable to get the Pico W to connect to my AWS endpoint. The response from the endpoint was zero bytes in length causing an error in the umqtt library.

I had converted the certificates correctly, as when verified using openssl s_client, the connection was fine.

I did notice that the s_client output indicated TLS v1.3, with a cipher of TLS_AES_128_GCM_SHA256. When using MicroPython ssl libray - SSLContext.get_ciphers(), I could not see this cipher enabled and I had no luck enabling it.

I don't know enough about TLS or SSL to know whether I am making an incorrect assumption about my connection issues, and so I defer to the AWS community members who may have more experienced input.

Solution The following steps worked for me:

On the AWS Console -> AWS IoT:

  1. Settings -> Device data endpoint I changed the 'Select security policy' dropdown option to 'IoTSecurityPolicy_TLS12_1_2_2022_10' for my endpoint. Did this make a difference?

  2. Security -> Policies Created a new policy from scratch (just to be sure). I made sure I had; iot:Connect, iot:Publish, iot:Subscribe, iot:Receive allowed for my testing purposes.

  3. All Devices -> Things - Create Thing I created a new 'thing' from scratch and selected the aforementioned policy in the new thing creation options.

  4. Downloaded The Certificates I downloaded all certificates during the 'thing' creation and converted the private key and certificate to DER using the openssl commands below (no change from previous attempts here):

openssl pkey -in .\xxxxxx-private.pem.key -out irrigation-control.private.key.der -outform DER
openssl x509 -in .\xxxxxx-certificate.pem.crt -out irrigation-control.certificate.der -outform DER

I tested the commands using the 'AmazonRootCA1.pem' with the converted DER format private key and certificate using the openssl commands below:

openssl s_client -connect xxxxxxxxxxxxxxx-ats.iot.eu-west-1.amazonaws.com:8883 -CAfile .\AmazonRootCA1.pem -cert .\irrigation-control.certificate.der -certform DER -key .\irrigation-control.private.key.der -keyform DER

I downloaded the Amazon Root CA 1 in DER format manually, from https://www.amazontrust.com/repository/. I wasn't sure whether previously, I was converting the original from PEM to DER correctly, and so I chose this safer option.

After the above, I was finally able to get a connection and see incoming messages on the MQTT test client.

Code example below:

import ntptime
import ssl

# after WiFi connection logic...

ntptime.settime()

MQTT_ENDPOINT = b"xxxxxxxxxxxxxxx-ats.iot.xx-xxxx-1.amazonaws.com"
MQTT_CLIENT_ID = b"xxxxxx-xxxxxx"

with open("certs/irrigation-control.private.key.der", "rb") as f:
    PRIVATE_KEY_DER = f.read()
with open("certs/irrigation-control.certificate.der", "rb") as f:
    CERT_DER = f.read()
# .cer file is DER format from https://www.amazontrust.com/repository/
with open("certs/AmazonRootCA1.cer", "rb") as f:
    CA_DER = f.read()

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(CERT_DER, PRIVATE_KEY_DER)
context.load_verify_locations(cadata=CA_DER)

mqtt_client = MQTTClient(
    client_id=MQTT_CLIENT_ID,
    server=MQTT_ENDPOINT,
    port=8883,
    keepalive=5000,
    ssl=context,
)

mqtt_client.connect()

print("Connected to AWS IoT...")

mqtt_client.set_callback(subscription_cb)
mqtt_client.subscribe("sdk/test/python")

while True:
    try:
        mqtt_client.publish(topic=b"test/irrigation", msg=b"HELLO", qos=0)
        sleep(5)
    except KeyboardInterrupt:
        print("Execution interrupted")
answered 7 months ago
0

Looking at your code, you seem to have copy-pasted it from this AWS Community Article:

https://community.aws/concepts/getting-started-with-pico-w-iot-core

There is a good walk-through in this article on how to create a thing, setup the IAM policies, download the certificates and how to copy them to the Raspberry device. If you follow these - you should be able to get this working as intended.

AWS
EXPERT
answered a year ago
0

If you use the latest version of umqtt.simple, i.e., 1.4.0, you need to modify the way to deal with ssl.

  • You can no longer include 'ssl_params' as a parameter for MQTTClient.
  • You need to set an instance of 'ssl.SSLContext' to 'ssl', not a boolean.
  • In addition, you have to set time by ntp.

Setting time:

import ntptime
ntptime.settime()

Preparing SSLContext:

import ssl
from umqtt.simple import MQTTClient
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(cafile='ca.der')
context.load_cert_chain('cert.der', 'key.der')

When you convert the PEM key to a DER format, you need to specify 'pkey', instead of 'rsa'.

openssl pkey -in key.pem -out key.der -outform DER 

Now you can get the instance of the client and connect to AWS IoT Core.

mqtt = MQTTClient(
    client_id=CLIENT_ID,
    server=AWS_ENDPOINT,
    port=8883,
    keepalive=5000,
    ssl = context
)
mqtt.connect()
answered 10 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions