Ambient Display of Air Quality

Since assembling the RGB LCD Shield, I’ve been thinking about what types of data could take advantage of being able to easily change the backlight color.  Air quality index is a good fit; AIRNow provides colored ranges for the index which indicate air quality at a broad level using green, yellow, and red.  I revisited the code for having the Internet of Things Printer fetch a data feed from Pachube and updated it to optionally track a single datastream within the feed.  I’m using:

Ambient AQI RGB LCD Parts

I inserted the pins on the shield stacking headers into the headers on the Arduino, then inserted the shield’s pins into the stacking headers.  The added height of the stacking headers means the RGB LCD shield clears the Ethernet jack on the Arduino; but, the overall structure is still stable and reasonably compact.

Ambient AQI RGB LCD Assembled

During setup, the backlight is set to teal to distinguish from the green / yellow / red of the actual data screens.  I’m also displaying the IP address for a few seconds to help with troubleshooting any network issues.

// Short delay so the IP can be spot checked.
lcd.setCursor(0, 1);

Ambient AQI RGB LCD Init Screen

With the backpack and accompanying library, it’s straightforward to change the color of the backlight.  These calls can be wrapped in an if-else structure to map ranges of values to different colors:

if (curValue <= 50) {
} else if (curValue <= 100) {
} else {

The result is that, at a glance, I can see that the air quality is good today.  If I’m interested, I can take a closer look and see when the data was collected and the exact value of the index.

Amient AQI RGB LCD Data Screen

Fortunately for me (but unfortunately for grabbing example images), our air quality was good every time I checked.  I ended up mocking up some data to show how things would look if the air quality were moderate.

Amient AQI RGB LCD Mock Moderate

And then again if the air quality were poor.

Ambient AQI RGB LCD Mock Poor

This general approach can be modified and used with a variety of data.  One example is display temperature and using blue to indicate when the temperature is too cold and red to indicate too hot.  It’s also possible to use a local sensor reading to generate the value which determines the backlight color.

Modifying the original code to handle datastream entries resulted in a few areas that need some explanation.  The most straightforward change was in the request URL; which now conditionally includes a datastream ID:

client->print("GET /v2/feeds/");
if (datastreamId != NULL) {

When working with the feed API, I made use of the Last-Modified header in the HTTP response to decide if the data had changed.  Unfortunately, the response for the datastream does not include a Last-Modified header field.  Fortunately, it does include an ETag header field which changes when the data changes.

if (datastreamId == NULL) {
    foundHeader = client->findUntil("Last-Modified:", "\r\n\r\n");
} else {
    foundHeader = client->findUntil("ETag:", "\r\n\r\n");

Finally, the original JSON parsing code viewed the end of the outermost object as the end of the parse and assumed all data had already been processed:

if(!depth) return true; // End of file
// process data

In order to handle a JSON feed where the outermost object contains the data, I changed this conditional to continue to the processing code and exit afterward if the flag variable isDataObjectRoot has been set (this flag defaults false, and is set to true if and only if a datastream ID has been specified):

if(!depth && !isDataObjectRoot) return true; // End of file
// process data
if(!depth && isDataObjectRoot) return true; // End of file

Otherwise, processing one datastream entry was very similar to processing the entire feed.  The full source code is available on GitHub and can be modified to track other data by opening the LCDFeed example and filling in the following fields:

char *apiKey = "";          // Developer API key
char *feedId = "";          // Feed ID
char *datastreamId = "";    // Datastream ID

I’m interested in seeing what other data can be displayed using this general approach and what types of conditions could be indicated by other colors.

As 2022 starts, let’s take some time to share our goals for CircuitPython in 2022. Just like past years (full summary 2019, 2020, and 2021), we’d like everyone in the CircuitPython community to contribute by posting their thoughts to some public place on the Internet. Here are a few ways to post: a video on YouTub, a post on the CircuitPython forum, a blog post on your site, a series of Tweets, a Gist on GitHub. We want to hear from you. When you post, please add #CircuitPython2022 and email to let us know about your post so we can blog it up here.

Stop breadboarding and soldering – start making immediately! Adafruit’s Circuit Playground is jam-packed with LEDs, sensors, buttons, alligator clip pads and more. Build projects with Circuit Playground in a few minutes with the drag-and-drop MakeCode programming site, learn computer science using the CS Discoveries class on, jump into CircuitPython to learn Python and hardware together, TinyGO, or even use the Arduino IDE. Circuit Playground Express is the newest and best Circuit Playground board, with support for CircuitPython, MakeCode, and Arduino. It has a powerful processor, 10 NeoPixels, mini speaker, InfraRed receive and transmit, two buttons, a switch, 14 alligator clip pads, and lots of sensors: capacitive touch, IR proximity, temperature, light, motion and sound. A whole wide world of electronics and coding is waiting for you, and it fits in the palm of your hand.

Join 32,000+ makers on Adafruit’s Discord channels and be part of the community!

Have an amazing project to share? The Electronics Show and Tell is every Wednesday at 7pm ET! To join, head over to YouTube and check out the show’s live chat – we’ll post the link there.

Join us every Wednesday night at 8pm ET for Ask an Engineer!

Follow Adafruit on Instagram for top secret new products, behinds the scenes and more

CircuitPython – The easiest way to program microcontrollers –

Maker Business — Pololu’s account of the chip shortage

Wearables — Stay frosty

Electronics — High voltage logic

Python for Microcontrollers — Python on Microcontrollers Newsletter: CircuitPython 2022 Survey, Python #1 in 2021 and more! #Python #CircuitPython @micropython @ThePSF

Adafruit IoT Monthly — 2021 in Recap!

Microsoft MakeCode — MakeCode Thank You!

EYE on NPI — Maxim’s Himalaya uSLIC Step-Down Power Module #EyeOnNPI @maximintegrated @digikey

New Products – Adafruit Industries – Makers, hackers, artists, designers and engineers! — New Products 1/12/22 Feat. Adafruit QT Py ESP32-S2 WiFi Dev Board with uFL Antenna Port – STEMMA QT!

Get the only spam-free daily newsletter about wearables, running a "maker business", electronic tips and more! Subscribe at !


  1. Making a display for a PCR machine would also work for this display

  2. I used the exact same shield and Arduino combo to fetch the weather data from our local airfield’s station here in the desert from
    If it’s yellow then it’s a sand/dust storm. Blue means less than 80F. Green is 80F-89F. Red is temps above 90F.
    Code is posted somewhere in the forums.

  3. @anonymous – Interesting idea, are you thinking in terms of measuring the current temperature in the PCR machine? Or some aspect of the cycle?

    @DavidM – I looked up your forum post and read through the code – I see in addition to using the color at that level you’re also using the text for details (type of dust condition, temperature, wind speed, etc.) Very nice!

  4. Chris O'Sullivan

    Nice, I assume you could use the same setup for any value such as pollen or mold count locally if there is a source of data for that.

  5. Chris – Thanks! Yup, should work for those types of values if there’s a source of data. I’m actually having trouble finding an official JSON APIs to fetch pollen/mold counts.

Sorry, the comment form is closed at this time.