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.

Adafruit publishes a wide range of writing and video content, including interviews and reporting on the maker market and the wider technology world. Our standards page is intended as a guide to best practices that Adafruit uses, as well as an outline of the ethical standards Adafruit aspires to. While Adafruit is not an independent journalistic institution, Adafruit strives to be a fair, informative, and positive voice within the community – check it out here:

Join Adafruit on Mastodon

Adafruit is on Mastodon, join in!

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.

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!

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

CircuitPython – The easiest way to program microcontrollers –

Maker Business — “Packaging” chips in the US

Wearables — Enclosures help fight body humidity in costumes

Electronics — Transformers: More than meets the eye!

Python for Microcontrollers — Python on Microcontrollers Newsletter: Silicon Labs introduces CircuitPython support, and more! #CircuitPython #Python #micropython @ThePSF @Raspberry_Pi

Adafruit IoT Monthly — Guardian Robot, Weather-wise Umbrella Stand, and more!

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! — #NewProds 7/19/23 Feat. Adafruit Matrix Portal S3 CircuitPython Powered Internet Display!

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.