CircuitPython Library Installation

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle matching your version of CircuitPython. PyPortal requires at least CircuitPython version 4.0.0.

Before continuing make sure your board's lib folder has the following files and folders copied over:

  • adafruit_aws_iot.mpy
  • adafruit_esp32spi
  • adafruit_requests.mpy
  • adafruit_bus_device
  • adafruit_logging.mpy
  • adafruit_seesaw
  • adafruit_display_text
  • adafruit_minimqtt.mpy
  • neopixel.mpy

Add CircuitPython Code and Project Assets

In the embedded code element below, click on the Download Project Bundle button and save the .zip archive file to your computer.

Then, uncompress the .zip file, it will unpack to a folder named PyPortal_AWS_IOT_Planter.

Copy the contents of PyPortal_AWS_IOT_Planter directory to your PyPortal CIRCUITPY drive.

# SPDX-FileCopyrightText: 2019 Brent Rubell for Adafruit Industries
# SPDX-License-Identifier: MIT

PyPortal Amazon AWS IoT Plant Monitor
Log your plant's vitals to AWS IoT and receive email
notifications when it needs watering with your PyPortal.

Author: Brent Rubell for Adafruit Industries, 2019
import os
import time
import json
import board
import busio
from digitalio import DigitalInOut
import neopixel
import adafruit_connection_manager
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_aws_iot import MQTT_CLIENT
from adafruit_seesaw.seesaw import Seesaw
import aws_gfx_helper

# Time between polling the STEMMA, in minutes

secrets = {
    "ssid" : os.getenv("CIRCUITPY_WIFI_SSID"),
    "password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"),

# Get device certificate
    with open("aws_cert.pem.crt", "rb") as f:
        DEVICE_CERT =
except ImportError:
    print("Certificate (aws_cert.pem.crt) not found on CIRCUITPY filesystem.")

# Get device private key
    with open("private.pem.key", "rb") as f:
        DEVICE_KEY =
except ImportError:
    print("Key (private.pem.key) not found on CIRCUITPY filesystem.")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)

# Verify nina-fw version >= 1.4.0
assert int(bytes(esp.firmware_version).decode("utf-8")[2]) >= 4, "Please update nina-fw to >=1.4.0."

status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(
    esp, secrets, status_light)

# Initialize the graphics helper
print("Loading AWS IoT Graphics...")
gfx = aws_gfx_helper.AWS_GFX()
print("Graphics loaded!")

# Set AWS Device Certificate

# Set AWS RSA Private Key

# Connect to WiFi
print("Connecting to WiFi...")

pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)

# Soil Sensor Setup
i2c_bus = busio.I2C(board.SCL, board.SDA)
ss = Seesaw(i2c_bus, addr=0x36)

# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connect(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print('Connected to AWS IoT!')
    print('Flags: {0}\nRC: {1}'.format(flags, rc))

    # Subscribe client to all shadow updates
    print("Subscribing to shadow updates...")

def disconnect(client, userdata, rc):
    # This method is called when the client disconnects
    # from the broker.
    print('Disconnected from AWS IoT!')

def subscribe(client, userdata, topic, granted_qos):
    # This method is called when the client subscribes to a new topic.
    print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos))

def unsubscribe(client, userdata, topic, pid):
    # This method is called when the client unsubscribes from a topic.
    print('Unsubscribed from {0} with PID {1}'.format(topic, pid))

def publish(client, userdata, topic, pid):
    # This method is called when the client publishes data to a topic.
    print('Published to {0} with PID {1}'.format(topic, pid))

def message(client, topic, msg):
    # This method is called when the client receives data from a topic.
    print("Message from {}: {}".format(topic, msg))

# Set up a new MiniMQTT Client
client =  MQTT.MQTT(broker = os.getenv("BROKER"),
                    client_id = os.getenv("CLIENT_ID"),

# Initialize AWS IoT MQTT API Client
aws_iot = MQTT_CLIENT(client)

# Connect callback handlers to AWS IoT MQTT Client
aws_iot.on_connect = connect
aws_iot.on_disconnect = disconnect
aws_iot.on_subscribe = subscribe
aws_iot.on_unsubscribe = unsubscribe
aws_iot.on_publish = publish
aws_iot.on_message = message

print('Attempting to connect to %s'

# Time in seconds since power on
initial = time.monotonic()

while True:
        gfx.show_aws_status('Listening for msgs...')
        now = time.monotonic()
        if now - initial > (SENSOR_DELAY * 60):
            # read moisture level
            moisture = ss.moisture_read()
            print("Moisture Level: ", moisture)
            # read temperature
            temperature = ss.get_temp()
            # Display Soil Sensor values on pyportal
            temperature = gfx.show_temp(temperature)
            print('Sending data to AWS IoT...')
            gfx.show_aws_status('Publishing data...')
            # Create a json-formatted device payload
            payload = {"state":{"reported":{"moisture":str(moisture),
            # Update device shadow
            gfx.show_aws_status('Data published!')
            print('Data sent!')
            # Reset timer
            initial = now
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data, retrying", e)

This is what the final contents of the CIRCUITPY drive will look like:


Install the Mu Editor 

This guide requires you to edit and interact with CircuitPython code. While you can use any text editor of your choosing,  Mu is a simple code editor that works with the Adafruit CircuitPython boards. It's written in Python and works on Windows, MacOS, Linux and Raspberry Pi. The serial console is built right in, so you get immediate feedback from your board's serial output!

Before proceeding, click the button below to install the Mu Editor. There are versions for PC, mac, and Linux.

Custom URL for AWS

Before you set up the settings.toml file for this project, you'll need to retrieve your AWS IOT's custom endpoint URL.

Navigate to your AWS IoT dashboard and click Settings on the sidebar. Your custom endpoint will be posted at the top of this page. Copy this value and save it somewhere safe, you'll need it in the next step.

Open the settings.toml file on your CIRCUITPY drive using Mu. You're going to edit the file to enter your local WiFi credentials along with data about your AWS IoT configuration.

Update the settings.toml file to include your WiFI credentials, and replace the BROKER value with the URL of your AWS IoT custom endpoint.

CIRCUITPY_WIFI_SSID = "your_wifi_ssid"
CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
TIMEZONE = "America/New_York"  #
BROKER = "your_AWS_IOT_endpoint_url"
CLIENT_ID = "PyPortal"

Adding your AWS IoT Device Certificate and Key to CIRCUITPY

CircuitPython makes loading your AWS IoT device certificate and private key as easy as drag and drop. Seriously - we're going to drag and drop the two files we downloaded earlier onto the filesystem.

AWS IoT names the certificate and RSA private key randomly (your private key file should look something like: 8a1018d558-private.pem.key).  You're going to rename the key/certificate so they'll be easier to reference them in the code.

Rename your key from RANDOMALPHANUMERICSTRING-private.pem.key to private.pem.key

Rename your certificate from RANDOMALPHANUMERICSTRING-certificate.pem.crt to aws_cert.pem.crt.

Then, copy them over to the CIRCUITPY volume. 

With the certificates copied over, you're good to go! Let's continue to the usage section.

This guide was first published on Oct 16, 2019. It was last updated on Jun 19, 2024.

This page (Code Setup) was last updated on Jun 19, 2024.

Text editor powered by tinymce.