CircuitPython Library Installation
First make sure you are running the latest version of Adafruit CircuitPython for your board.
Add CircuitPython Code and Project Assets
In the embedded code element below, click on the Download Project Bundle button below, and save the .zip archive file to your computer.
Then, uncompress the .zip file, it will unpack to a folder named pyportal_pet_planter.
Copy the contents of pyportal_pet_planter directory to your PyPortal CIRCUITPY drive.
Be sure to copy the fonts, images, and sounds directories from the Project Bundle zip file to the PyPortal CIRCUITPY drive in addition to the libraries in lib and code.py.
# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries # # SPDX-License-Identifier: MIT import os import time import board import busio from digitalio import DigitalInOut import adafruit_connection_manager from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager import adafruit_imageload import displayio import neopixel from adafruit_bitmap_font import bitmap_font from adafruit_display_text.label import Label from adafruit_io.adafruit_io import IO_MQTT import adafruit_minimqtt.adafruit_minimqtt as MQTT from adafruit_pyportal import PyPortal from adafruit_seesaw.seesaw import Seesaw from simpleio import map_range #---| User Config |--------------- # How often to poll the soil sensor, in seconds # Polling every 30 seconds or more may cause connection timeouts DELAY_SENSOR = 15 # How often to send data to adafruit.io, in minutes DELAY_PUBLISH = 5 # Maximum soil moisture measurement SOIL_LEVEL_MAX = 500.0 # Minimum soil moisture measurement SOIL_LEVEL_MIN= 350.0 #---| End User Config |--------------- # Background image BACKGROUND = "/images/roots.bmp" # Icons for water level and temperature ICON_LEVEL = "/images/icon-wetness.bmp" ICON_TEMP = "/images/icon-temp.bmp" WATER_COLOR = 0x16549E # Audio files wav_water_high = "/sounds/water-high.wav" wav_water_low = "/sounds/water-low.wav" # the current working directory (where this file is) cwd = ("/"+__file__).rsplit('/', 1)[0] secrets = { "ssid" : os.getenv("CIRCUITPY_WIFI_SSID"), "password" : os.getenv("CIRCUITPY_WIFI_PASSWORD"), } # Set up i2c bus i2c_bus = busio.I2C(board.SCL, board.SDA) # Initialize soil sensor (s.s) ss = Seesaw(i2c_bus, addr=0x36) # PyPortal ESP32 AirLift Pins 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) wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) # Initialize PyPortal Display display = board.DISPLAY WIDTH = board.DISPLAY.width HEIGHT = board.DISPLAY.height # Initialize new PyPortal object pyportal = PyPortal(esp=esp, external_spi=spi) # Set backlight level pyportal.set_backlight(0.5) # Create a new DisplayIO group splash = displayio.Group() # show splash group display.root_group = splash # Palette for water bitmap palette = displayio.Palette(2) palette[0] = 0x000000 palette[1] = WATER_COLOR palette.make_transparent(0) # Create water bitmap water_bmp = displayio.Bitmap(display.width, display.height, len(palette)) water = displayio.TileGrid(water_bmp, pixel_shader=palette) splash.append(water) print("drawing background..") # Load background image try: bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND, bitmap=displayio.Bitmap, palette=displayio.Palette) # Or just use solid color except (OSError, TypeError): BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000 bg_bitmap = displayio.Bitmap(display.width, display.height, 1) bg_palette = displayio.Palette(1) bg_palette[0] = BACKGROUND bg_palette.make_transparent(0) background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette) # Add background to display splash.append(background) print('loading fonts...') # Fonts within /fonts/ folder font = cwd+"/fonts/GothamBlack-50.bdf" font_small = cwd+"/fonts/GothamBlack-25.bdf" # pylint: disable=syntax-error data_glyphs = b'0123456789FC-* ' font = bitmap_font.load_font(font) font.load_glyphs(data_glyphs) font_small = bitmap_font.load_font(font_small) full_glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: ' font_small.load_glyphs(full_glyphs) # Label to display Adafruit IO status label_status = Label(font_small) label_status.x = 305 label_status.y = 10 splash.append(label_status) # Create a label to display the temperature label_temp = Label(font) label_temp.x = 35 label_temp.y = 300 splash.append(label_temp) # Create a label to display the water level label_level = Label(font) label_level.x = display.width - 130 label_level.y = 300 splash.append(label_level) print('loading icons...') # Load temperature icon icon_tmp_bitmap, icon_palette = adafruit_imageload.load(ICON_TEMP, bitmap=displayio.Bitmap, palette=displayio.Palette) icon_palette.make_transparent(0) icon_tmp_bitmap = displayio.TileGrid(icon_tmp_bitmap, pixel_shader=icon_palette, x=0, y=280) splash.append(icon_tmp_bitmap) # Load level icon icon_lvl_bitmap, icon_palette = adafruit_imageload.load(ICON_LEVEL, bitmap=displayio.Bitmap, palette=displayio.Palette) icon_palette.make_transparent(0) icon_lvl_bitmap = displayio.TileGrid(icon_lvl_bitmap, pixel_shader=icon_palette, x=315, y=280) splash.append(icon_lvl_bitmap) # Connect to WiFi label_status.text = "Connecting..." while not esp.is_connected: try: wifi.connect() except (RuntimeError, ConnectionError) as e: print("could not connect to AP, retrying: ",e) wifi.reset() continue print("Connected to WiFi!") pool = adafruit_connection_manager.get_radio_socketpool(esp) ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp) # Initialize a new MQTT Client object mqtt_client = MQTT.MQTT(broker="io.adafruit.com", username=os.getenv("AIO_USERNAME"), password=os.getenv("AIO_KEY"), socket_pool=pool, ssl_context=ssl_context) # Adafruit IO Callback Methods # pylint: disable=unused-argument def connected(client): # Connected function will be called when the client is connected to Adafruit IO. print('Connected to Adafruit IO!') def subscribe(client, userdata, topic, granted_qos): # This method is called when the client subscribes to a new feed. print('Subscribed to {0} with QOS level {1}'.format(topic, granted_qos)) # pylint: disable=unused-argument def disconnected(client): # Disconnected function will be called if the client disconnects # from the Adafruit IO MQTT broker. print("Disconnected from Adafruit IO!") # Initialize an Adafruit IO MQTT Client io = IO_MQTT(mqtt_client) # Connect the callback methods defined above to the Adafruit IO MQTT Client io.on_connect = connected io.on_subscribe = subscribe io.on_disconnect = disconnected # Connect to Adafruit IO print("Connecting to Adafruit IO...") io.connect() label_status.text = " " print("Connected!") fill_val = 0.0 def fill_water(fill_percent): """Fills the background water. :param float fill_percent: Percentage of the display to fill. """ assert fill_percent <= 1.0, "Water fill value may not be > 100%" # pylint: disable=global-statement global fill_val if fill_val > fill_percent: for _y in range(int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_val)), int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent))): for _x in range(1, board.DISPLAY.width-1): water_bmp[_x, _y] = 0 else: for _y in range(board.DISPLAY.height-1, (board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent), -1): for _x in range(1, board.DISPLAY.width-1): water_bmp[_x, _y] = 1 fill_val = fill_percent def display_temperature(temp_val, is_celsius=False): """Displays the temperature from the STEMMA soil sensor on the PyPortal Titano. :param float temp: Temperature value. :param bool is_celsius: """ if not is_celsius: temp_val = (temp_val * 9 / 5) + 32 - 15 print('Temperature: %0.0fF'%temp_val) label_temp.text = '%0.0fF'%temp_val return int(temp_val) else: print('Temperature: %0.0fC'%temp_val) label_temp.text = '%0.0fC'%temp_val return int(temp_val) # initial reference time initial = time.monotonic() while True: # Explicitly pump the message loop # to keep the connection active try: io.loop() except (ValueError, RuntimeError, ConnectionError, OSError) as e: print("Failed to get data, retrying...\n", e) wifi.reset() continue now = time.monotonic() print("reading soil sensor...") # Read capactive moisture = ss.moisture_read() label_level.text = str(moisture) # Convert into percentage for filling the screen moisture_percentage = map_range(float(moisture), SOIL_LEVEL_MIN, SOIL_LEVEL_MAX, 0.0, 1.0) # Read temperature temp = ss.get_temp() temp = display_temperature(temp) # fill display print("filling disp..") fill_water(moisture_percentage) print("disp filled..") print("temp: " + str(temp) + " moisture: " + str(moisture)) # Play water level alarms if moisture <= SOIL_LEVEL_MIN: print("Playing low water level warning...") pyportal.play_file(wav_water_low) elif moisture >= SOIL_LEVEL_MAX: print("Playing high water level warning...") pyportal.play_file(wav_water_high) if now - initial > (DELAY_PUBLISH * 60): try: print("Publishing data to Adafruit IO...") label_status.text = "Sending to IO..." io.publish("moisture", moisture) io.publish("temperature", temp) print("Published") label_status.text = "Data Sent!" # reset timer initial = now except (ValueError, RuntimeError, ConnectionError, OSError) as e: label_status.text = "ERROR!" print("Failed to get data, retrying...\n", e) wifi.reset() time.sleep(DELAY_SENSOR)
This is what the final contents of the CIRCUITPY drive will look like:

Before continuing make sure your board's lib folder has the following files and folders copied over:
- adafruit_bitmap_font
- adafruit_io
- adafruit_bus_device
- adafruit_logging.mpy
- adafruit_seesaw
- adafruit_display_text
- adafruit_minimqtt.mpy
- adafruit_touchscreen.mpy
- adafruit_esp32spi
- adafruit_pyportal.mpy
- neopixel.mpy
- adafruit_imageload
- adafruit_requests.mpy
- simpleio.mpy
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.
settings.toml File Setup
Open the settings.toml file on your CIRCUITPY drive using Mu or your own text editor. If you don't have one, use a text editor and copy the text below and edit from there. Add the credentials shown below, substituting your own credentials.
CIRCUITPY_WIFI_SSID = "your_wifi_ssid" CIRCUITPY_WIFI_PASSWORD = "your_wifi_password" AIO_USERNAME = "your_aio_username" AIO_KEY = "your_aio_key"
Page last edited January 22, 2025
Text editor powered by tinymce.