Libraries
We'll need to make sure we have these libraries installed. (Check out this link on installing libraries if needed.)
- adafruit_bitmap_font
- adafruit_bus_device
- adafruit_display_text
- adafruit_esp32spi
- adafruit_io
- adafruit_matrixportal
- adafruit_requests.mpy
- neopixel.mpy
Connect to the Internet
Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. The process for connecting can be found here.
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files.
Code
Click the Download: Zip File link below in the code window to get a zip file with all the files needed for the project. Copy code.py from the zip file and place it on the CIRCUITPY drive.
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries # # SPDX-License-Identifier: MIT # Purple Air AQI Display # for Metro M4 Airlift with RGB Matrix Shield # or Matrix Portal # and 64 x 32 RGB LED Matrix import time import board import terminalio from adafruit_matrixportal.matrixportal import MatrixPortal # 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 def aqi_transform(val): aqi = pm_to_aqi(val) # derive Air Quality Index from Particulate Matter 2.5 value return "AQI: %d" % aqi def message_transform(val): # picks message based on thresholds index = aqi_to_list_index(pm_to_aqi(val)) messages = ( "Hazardous", "Very Unhealthy", "Unhealthy", "Unhealthy for Sensitive Groups", "Moderate", "Good", ) if index is not None: return messages[index] return "Unknown" SENSOR_ID = 3085 # Poughkeepsie # 30183 LA outdoor / 37823 oregon / 21441 NYC SENSOR_REFRESH_PERIOD = 300 # seconds DATA_SOURCE = f"https://api.purpleair.com/v1/sensors/{SENSOR_ID}?fields=pm2.5_10minute" SCROLL_DELAY = 0.02 DATA_LOCATION = ["sensor", "stats", "pm2.5_10minute"] # navigate the JSON response # --- Display setup --- matrixportal = MatrixPortal( status_neopixel=board.NEOPIXEL, debug=True, url=DATA_SOURCE, headers={"X-API-Key": secrets["purple_air_api_key"], # purpleair.com "Accept": "application/json" }, json_path=(DATA_LOCATION, DATA_LOCATION), ) # Create a static label to show AQI matrixportal.add_text( text_font=terminalio.FONT, text_position=(8, 7), text_transform=aqi_transform, ) # Create a scrolling label to show level message matrixportal.add_text( text_font=terminalio.FONT, text_position=(0, 23), scrolling=True, text_transform=message_transform, ) # pylint: disable=too-many-return-statements def aqi_to_list_index(aqi): aqi_groups = (301, 201, 151, 101, 51, 0) for index, group in enumerate(aqi_groups): if aqi >= group: return index return None def calculate_aqi(Cp, Ih, Il, BPh, BPl): # wikipedia.org/wiki/Air_quality_index#Computing_the_AQI return round(((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il) def pm_to_aqi(pm): pm = float(pm) if pm < 0: return pm if pm > 1000: return 1000 if pm > 350.5: return calculate_aqi(pm, 500, 401, 500, 350.5) elif pm > 250.5: return calculate_aqi(pm, 400, 301, 350.4, 250.5) elif pm > 150.5: return calculate_aqi(pm, 300, 201, 250.4, 150.5) elif pm > 55.5: return calculate_aqi(pm, 200, 151, 150.4, 55.5) elif pm > 35.5: return calculate_aqi(pm, 150, 101, 55.4, 35.5) elif pm > 12.1: return calculate_aqi(pm, 100, 51, 35.4, 12.1) elif pm >= 0: return calculate_aqi(pm, 50, 0, 12, 0) else: return None def get_color(aqi): index = aqi_to_list_index(aqi) colors = ( (115, 20, 37), (140, 26, 75), (234, 51, 36), (239, 133, 51), (255, 255, 85), (104, 225, 67), ) if index is not None: return colors[index] return (150, 150, 150) sensor_refresh = None while True: # only query the weather every 10 minutes (and on first run) if (not sensor_refresh) or (time.monotonic() - sensor_refresh) > SENSOR_REFRESH_PERIOD: try: value = matrixportal.fetch() print("Response is", value) matrixportal.set_text_color(get_color(pm_to_aqi(value[0]))) sensor_refresh = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) continue # Scroll it matrixportal.scroll_text(SCROLL_DELAY)
Purple Air
Purple Air is an air quality monitoring network based on IoT sensors. You can learn more about the sensors here. When people buy and install their own sensors and enable them to appear on the Purple Air map, we all benefit!
Head to the Purple Air map here and then try to find a sensor near your location. Even if you don't have your own sensor installed outside, you may be able to find a sensor nearby that you can choose to monitor on your Matrix display.
Here, I've zoomed in on a sensor in downtown Los Angeles. If I look at the URL for this sensor, I can see the sensor ID number:
https://www.purpleair.com/map?opt=1/mAQI/a10/cC0&select=30183
The number after select=
is 30183
so I'll copy than number to use in my code.py file so that I can display that air quality sensor value.
In your code.py file, go to the line that has the variable SENSOR_ID
and paste in the number of the sensor you'd like to display, then re-save the file to your CIRCUITPY drive.
For example:
SENSOR_ID = 30183
API Key
Purple Air made a change in 2022 that now requires an API Key. To get a key, you'll need to head over to https://develop.purpleair.com/keys and sign in with a Google Account. When you first sign up, your new account should have 1,000,000 free points. A new project will automatically be created for you. When generating your API Key, that project is selected by default. Read Access will also be selected for the API key, which is all you'll need.
Copy and paste this key into your secrets.py file that is on the root level of your CIRCUITPY drive, so it looks something like this:
secrets = { 'ssid' : 'your_wifi_ssid', 'password' : 'your_wifi_password', 'purple_air_api_key' : 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }
You will also need to assign points to the project before you can use the API key, otherwise you will get a Payment Required error message. Each API call uses up points that can vary depending on the amount of data returned and how frequently it is called. The code specifies which fields to include in order to minimize the cost. It is also set to check every 10 minutes by default. Increasing the frequency by lowering SENSOR_REFRESH_PERIOD
will use up points faster. Once you have used up all of your points, it will require purchasing more points. You can limit the amount of points per a project by only assigning a portion of them.
How it Works
Libraries
Most of the heavy lifting is done by the adafruit_matrixportal
library we import. This will take care of getting online through the access point (the info is in your secrets.py file), as well as fetching and parsing the PM2.5 particle data from the Purple Air JSON file.
Functions
The aqi_transform()
function converts PM (particulate matter) values to AQI (air quality index) values and returns the text string we'll display. It does this by using both the pm_to_aqi()
and the calculate_aqi()
fuctions.
The message_transform()
function selects the text description associated with a give AQI value.
Variables
These are the variables used to pick a Purple Air sensor as well as a refresh rate, data source, scroll speed, and the data location withing the JSON file:
SENSOR_ID = 3085 # Poughkeepsie SENSOR_REFRESH_PERIOD = 300 # seconds DATA_SOURCE = f"https://api.purpleair.com/v1/sensors/{SENSOR_ID}?fields=pm2.5_10minute" SCROLL_DELAY = 0.02 DATA_LOCATION = ["sensor", "stats", "pm2.5_10minute"] # navigate the JSON response
Main Loop
In the main loop of the code the latest value from the selected Purple Air sensor is fetched using value = matrixportal.fetch()
and then the result is formatted and updated to the display.
The text description is then scrolled across the display and the process is repeated every thirty seconds.
Page last edited January 15, 2025
Text editor powered by tinymce.