Code Setup

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 Zip link, 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's CIRCUITPY drive.

"""
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 time
import json
import board
import busio
from digitalio import DigitalInOut
import neopixel
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_minimqtt import 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
SENSOR_DELAY = 15

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

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

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

# 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
esp.set_certificate(DEVICE_CERT)

# Set AWS RSA Private Key
esp.set_private_key(DEVICE_KEY)

# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected!")

# 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...")
    aws_iot.shadow_subscribe()

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(socket,
               broker = secrets['broker'],
               client_id = secrets['client_id'],
               network_manager = wifi)

# 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'%client.broker)
aws_iot.connect()

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

while True:
    try:
        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)
            gfx.show_water_level(moisture)
            print('Sending data to AWS IoT...')
            gfx.show_aws_status('Publishing data...')
            # Create a json-formatted device payload
            payload = {"state":{"reported":{"moisture":str(moisture),
                                            "temp":str(temperature)}}}
            # Update device shadow
            aws_iot.shadow_update(json.dumps(payload))
            gfx.show_aws_status('Data published!')
            print('Data sent!')
            # Reset timer
            initial = now
        aws_iot.loop()
    except (ValueError, RuntimeError) as e:
        print("Failed to get data, retrying", e)
        wifi.reset()

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.

Secrets File Setup

Before you set up the secrets 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 secrets.py 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.

Make the following changes to the code below in the secrets.py file:

  • ReplaceMY_WIFI_SSIDwith the name of your WiFi SSID
  • ReplaceMY_WIFI_PASSWORDwith your WiFi's password
  • Replacebroker with the URL of your AWS IoT custom endpoint
Download: file
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

secrets = {
    'ssid' : 'MY_WIFI_SSID',
    'password' : 'MY_WIFI_PASSWORD',
    "timezone" : "America/New_York",  # http://worldtimeapi.org/timezones
    "broker" : "MY_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 Oct 16, 2019. This page (Code Setup) was last updated on Jan 27, 2020.