import os 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
The code first imports all of the modules required to run the code. Some of these libraries are CircuitPython core modules (they're "burned into" the firmware) and some of them you dragged into the library folder (lib on the PyPortal's CIRCUITPY drive).
The code for this project imports a special adafruit_aws_iot
library. To help simplify managing communication between your PyPortal and AWS IoT's MQTT API, we wrote a CircuitPython helper module called Adafruit_CircuitPython_AWS_IOT
- For more information about using the MQTT protocol with CircuitPython - check out our MQTT in CircuitPython guide on this topic here.
We've also included a aws_gfx_helper.py file which handles displaying the status of the code on the PyPortal's display.
The next chunk of code grabs information from your settings.tom file about your WiFi AP configuration, AWS device identifier and AWS IoT endpoint. The device certificate and RSA private key are read into variables, DEVICE_CERT
and DEVICE_KEY
.
# 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
Then, it sets up the ESP32's SPI connections for use with the PyPortal along with a wifi
manager for interfacing with the ESP32.
# 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) status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager( esp, secrets, status_light)
Configuring the Graphical Helper
The graphics helper, which manages' the PyPortal's display, is created. If you wish to display the temperature in Fahrenheit instead of Celsius, add is_celsius=True
to the method call.
# Initialize the graphics helper print("Loading AWS IoT Graphics...") gfx = aws_gfx_helper.AWS_GFX() print("Graphics loaded!")
Connecting to WiFi and AWS IoT
Prior to establishing a connection with the AWS MQTT broker, we'll use the esp
object to set the AWS device certificate and private key.
# Set AWS Device Certificate esp.set_certificate(DEVICE_CERT) # Set AWS RSA Private Key esp.set_private_key(DEVICE_KEY)
Once the certificate and private key have been set, we can connect to the WiFi network and the AWS IoT MQTT broker.
# Connect to WiFi print("Connecting to WiFi...") wifi.connect() print("Connected!")
Configure the STEMMA Sensor
An I2C busio
device is set up and linked to the soil sensor's address (0x36
).
# Soil Sensor Setup i2c_bus = busio.I2C(board.SCL, board.SDA) ss = Seesaw(i2c_bus, addr=0x36)
MQTT Connection Callback Methods
The following methods are used as MQTT client callbacks. They only execute when the broker (AWS IoT MQTT) communicates with your PyPortal.
- For a complete explanation of how MiniMQTT's callback methods work, click here.
# 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))
Connecting to AWS IoT
The code first initializes the AWS MQTT client with the endpoint identifier (broker
) and device identifier (client_id
).
# Set up a new MiniMQTT Client client = MQTT(socket, broker = os.getenv("BROKER"), client_id = os.getenv("CLIENT_ID"), network_manager = wifi) # Initialize AWS IoT MQTT API Client aws_iot = MQTT_CLIENT(client)
The connection callback methods created earlier are connected to the aws_iot
client and the code attempts to connect to AWS IoT.
# 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()
Once AWS IoT's MQTT broker successfully connects with your client, it'll call the connect()
callback method. This method subscribes to the device's shadow topic and listens for updates (aws_iot.shadow_subscribe()
). Any data sent to this topic will be received by the code's message()
callback.
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()
Main Loop
The main loop takes the current time and compares it to the desired SENSOR_DELAY
time in minutes (set at the top of the code).
If the time has exceeded SENSOR_DELAY
, the code reads the moisture level and temperature from the STEMMA soil sensor. Then, it displays the values of the soil sensor on the PyPortal using the gfx
module.
# 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 > (0.1 * 60): # read moisture level moisture = ss.moisture_read() print("Moisture Level: ", moisture) # read temperature temperature = ss.get_temp() print("Temperature:{}F".format(temperature)) # Display Soil Sensor values on pyportal temperature = gfx.show_temp(temperature) gfx.show_water_level(moisture)
We create a JSON-formatted payload (AWS device shadows require this format) to hold both the moisture and temperature. Then, we update the shadow using the handy shadow_update()
helper method from the CircuitPython AWS IoT library.
We'll update the display to show data has been published to AWS IoT and set the timer to the current time.monotonic
value.
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
If the SENSOR_DELAY
time has not yet elapsed, we'll poll the AWS MQTT broker to ensure we retain communication with the broker. aws_iot.loop()
pings AWS IOT's MQTT broker and listenings for a response back from it. It also queries the broker for any messages received.
All of this code is wrapped inside a try/except control flow. If the WiFi module fails at any point, the program will execute the except
and reset the module before going back to the top of the try
.
Page last edited April 09, 2024
Text editor powered by tinymce.