Once you've finished setting up your Qualia S3 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import time import os import ssl import io import binascii import jpegio import microcontroller import wifi import socketpool import displayio from adafruit_qualia.graphics import Graphics, Displays import adafruit_minimqtt.adafruit_minimqtt as MQTT aio_username = os.getenv("ADAFRUIT_AIO_USERNAME") aio_key = os.getenv("ADAFRUIT_AIO_KEY") print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") camera_feed = aio_username + "/feeds/camera" graphics = Graphics(Displays.ROUND40, default_bg=None, auto_refresh=True) def center(g, b): # center the image g.x -= ((b.width * 2) - 720) // 4 g.y -= ((b.height * 2) - 720) // 4 def decode_image(base64_msg): # Decode the base64 image into raw binary JPEG data decoded_image = binascii.a2b_base64(base64_msg) # Create a JpegDecoder instance decoder = jpegio.JpegDecoder() # Use io.BytesIO to treat the decoded image as a file-like object jpeg_data = io.BytesIO(decoded_image) # Open the JPEG data source from the BytesIO object width, height = decoder.open(jpeg_data) print(width, height) # Create a Bitmap with the dimensions of the JPEG image bitmap = displayio.Bitmap(width, height, 65536) # Use 65536 colors for RGB565 # Decode the JPEG into the bitmap decoder.decode(bitmap) # pylint: disable=line-too-long grid = displayio.TileGrid(bitmap, pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565_SWAPPED)) center(grid, bitmap) group = displayio.Group(scale=2) group.append(grid) graphics.display.root_group = group graphics.display.refresh() # Define callback methods which are called when events occur def connected(client, userdata, flags, rc): # pylint: disable=unused-argument # This function will be called when the client is connected # successfully to the broker. print(f"Connected to Adafruit IO! Listening for topic changes on {camera_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(camera_feed) def disconnected(client, userdata, rc): # pylint: disable=unused-argument # This method is called when the client is disconnected print("Disconnected from Adafruit IO!") def message(client, topic, msg): # pylint: disable=unused-argument # This method is called when a topic the client is subscribed to # has a new message. print(f"New message on topic {topic}") decode_image(msg) pool = socketpool.SocketPool(wifi.radio) ssl_context = ssl.create_default_context() # Initialize an Adafruit IO HTTP API object mqtt_client = MQTT.MQTT( broker="io.adafruit.com", port=1883, username=aio_username, password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) # Setup the callback methods above mqtt_client.on_connect = connected mqtt_client.on_disconnect = disconnected mqtt_client.on_message = message # Connect the client to the MQTT broker. print("Connecting to Adafruit IO...") mqtt_client.connect() while True: # Poll the message queue try: mqtt_client.loop(timeout=1) time.sleep(5) except Exception as error: # pylint: disable=broad-except print(error) time.sleep(5) microcontroller.reset()
Upload the Code and Libraries to the Qualia S3
After downloading the Project Bundle, plug your Qualia S3 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the Qualia S3's CIRCUITPY drive.
- lib folder
- code.py
Your Qualia S3 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.
Add Your settings.toml File
As of CircuitPython 8.0.0, there is support for Environment Variables. Environment variables are stored in a settings.toml file. Similar to secrets.py, the settings.toml file separates your sensitive information from your main code.py file. Add your settings.toml file as described in the Create Your settings.toml File page earlier in this guide. You'll need to include your ADAFRUIT_AIO_USERNAME
, ADAFRUIT_AIO_KEY
, CIRCUITPY_WIFI_SSID
and CIRCUITPY_WIFI_PASSWORD
.
CIRCUITPY_WIFI_SSID = "your-ssid-here" CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here" ADAFRUIT_AIO_USERNAME = "your-username-here" ADAFRUIT_AIO_KEY = "your-key-here"
How the CircuitPython Code Works
At the top of the code, your Adafruit IO credentials are imported from the settings.toml file. A WiFi connection is established using your SSID and SSID password from the settings.toml file. The Adafruit IO feed that will be monitored via MQTT is passed to camera_feed
. You can change the feed name here if you prefer to use a feed that isn't called "camera".
aio_username = os.getenv("ADAFRUIT_AIO_USERNAME") aio_key = os.getenv("ADAFRUIT_AIO_KEY") print(f"Connecting to {os.getenv('CIRCUITPY_WIFI_SSID')}") wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) print(f"Connected to {os.getenv('CIRCUITPY_WIFI_SSID')}!") camera_feed = aio_username + "/feeds/camera"
Graphics
The 720x720 round display is instantiated using the adafruit_qualia library. Two functions are used for graphics. The first is a simple function called center()
that centers the image on the display.
The second is called decode_image()
. This function takes the Base64 encoded image from the Adafruit IO feed, decodes it using jpegio and displays it as a bitmap on the display.
You'll notice that the displayio group
is set to scale=2
. This lets you use a smaller image to stay within the data size limits of Adafruit IO feeds (100KB with no feed history) while still filling up the entire display. You'll see on the Apple Shortcut page that the images are being resized to a width of 360, half of 720.
graphics = Graphics(Displays.ROUND40, default_bg=None, auto_refresh=True) def center(g, b): # center the image g.x -= ((b.width * 2) - 720) // 4 g.y -= ((b.height * 2) - 720) // 4 def decode_image(base64_msg): # Decode the base64 image into raw binary JPEG data decoded_image = binascii.a2b_base64(base64_msg) # Create a JpegDecoder instance decoder = jpegio.JpegDecoder() # Use io.BytesIO to treat the decoded image as a file-like object jpeg_data = io.BytesIO(decoded_image) # Open the JPEG data source from the BytesIO object width, height = decoder.open(jpeg_data) print(width, height) # Create a Bitmap with the dimensions of the JPEG image bitmap = displayio.Bitmap(width, height, 65536) # Use 65536 colors for RGB565 # Decode the JPEG into the bitmap decoder.decode(bitmap) # pylint: disable=line-too-long grid = displayio.TileGrid(bitmap, pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565_SWAPPED)) center(grid, bitmap) group = displayio.Group(scale=2) group.append(grid) graphics.display.root_group = group graphics.display.refresh()
MQTT
The MQTT setup portion of the code is based on the MQTT CircuitPython example. It sets up functions to subscribe to your Adafruit IO feed, disconnect from Adafruit IO and receive a new message from the subscribed feed. In the message()
function, the decode_image()
function is called, passing the msg
data from the feed. mqtt_client
is instantiated to use Adafruit IO as the broker and pass your Adafruit IO credentials.
def connected(client, userdata, flags, rc): # pylint: disable=unused-argument # This function will be called when the client is connected # successfully to the broker. print(f"Connected to Adafruit IO! Listening for topic changes on {camera_feed}") # Subscribe to all changes on the onoff_feed. client.subscribe(camera_feed) def disconnected(client, userdata, rc): # pylint: disable=unused-argument # This method is called when the client is disconnected print("Disconnected from Adafruit IO!") def message(client, topic, msg): # pylint: disable=unused-argument # This method is called when a topic the client is subscribed to # has a new message. print(f"New message on topic {topic}") decode_image(msg) pool = socketpool.SocketPool(wifi.radio) ssl_context = ssl.create_default_context() # Initialize an Adafruit IO HTTP API object mqtt_client = MQTT.MQTT( broker="io.adafruit.com", port=1883, username=aio_username, password=aio_key, socket_pool=pool, ssl_context=ssl_context, ) # Setup the callback methods above mqtt_client.on_connect = connected mqtt_client.on_disconnect = disconnected mqtt_client.on_message = message # Connect the client to the MQTT broker. print("Connecting to Adafruit IO...") mqtt_client.connect()
The Loop
MQTT makes the loop very simple. mqtt_client.loop(timeout=1)
with a slight delay is wrapped in a try
/except
to reset the board in case of any errors. Using this method, anytime a new photo is sent to your Adafruit IO feed, it will run the decode_image()
function and update the display to show the new photo.
while True: # Poll the message queue try: mqtt_client.loop(timeout=1) time.sleep(5) except Exception as error: # pylint: disable=broad-except print(error) time.sleep(5) microcontroller.reset()
Text editor powered by tinymce.