Code Walkthrough

Importing CircuitPython Libraries

Download: file
import board
import busio
import time
from digitalio import DigitalInOut
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import neopixel
from adafruit_azureiot import IOT_Hub
from adafruit_seesaw.seesaw import Seesaw

# gfx helper
import azure_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.

The code for this project imports a special adafruit_azureiot library. To help simplify communication between your device and Azure IoT's REST API, we wrote a CircuitPython helper module called Adafruit_CircuitPython_AzureIoT

We've also included an azure_gfx_helper.py file which handles displaying the status of the code on the PyPortal's display.

Configuring the PyPortal's WiFi

The next chunk of code grabs information from a secrets.py file including wifi configuration. Then, it sets up the ESP32's SPI connections for use with the PyPortal. The wifi object is set up here too - it's used later in the code to communicate with the IoT Hub.

Download: file
# 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

# PyPortal ESP32 Setup
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
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) # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
#status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

Configuring the Soil Sensor

An I2C busio device is set up and linked to the soil sensor's address (0x36)

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

Configuring the Azure IoT Module

Next, we'll create an instance of the Azure IoT Hub called hub. It takes in the WiFiManager object, wifi, the name of the IoT hub, the hub's SAS token, and the name of the device sending data to the hub.

Download: file
# Create an instance of the Azure IoT Hub
hub = IOT_Hub(wifi, secrets['azure_iot_hub'], secrets['azure_iot_sas'], secrets['azure_device_id'])

Configuring the Graphical Helper 

The graphics helper, which manages' the PyPortal's display is created. If you want to show the temperature in Fahrenheit instead of Celsius, change is_celsius=False to is_celsius=True.

Download: file
# init. graphics helper
gfx = azure_gfx_helper.Azure_GFX(is_celsius=False)

Main Loop

The first part of the main loop reads the soil sensor's (ss) moisture level and temperature. It then calls gfx.display_moisture and gfx.display_temp to display the moisture and temperature values on the PyPortal's display

Download: file
# read moisture level
moisture_level = ss.moisture_read()
# read temperature
temperature = ss.get_temp()
# display soil sensor values on pyportal
gfx.display_moisture(moisture_level)
temperature = gfx.display_temp(temperature)

The next chunk of code changes the display's status text to indicate that the data is being sent to the Azure hub. Next we have two hub.send_device_message() calls - one to send the stemma soil sensor's temperature and one to send the moisture_level.

After the two send_device_message calls are completed, the Azure status text area is changed to indicate that data has been sent.

Download: file
print('Sending data to Azure')
gfx.display_azure_status('Sending data...')
hub.send_device_message(temperature)
hub.send_device_message(moisture_level)
gfx.display_azure_status('Data sent!')
print('Data sent!')

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.

Finally, the code will "sleep" for sixty seconds. If you'd like to make a longer delay, simply increase the value inside the call to time.sleep().

Download: file
except (ValueError, RuntimeError) as e:
  time.sleep(60)
This guide was first published on May 24, 2019. It was last updated on May 24, 2019. This page (Code Walkthrough) was last updated on Oct 17, 2019.