# IoT Air Quality Sensor with Adafruit IO

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/095/947/medium800/temperature___humidity_D44F5BB0-1BEF-48C4-AAF2-FCB854976FA5_1_201_a.jpeg?1602875145)

Breathe easy, knowing that you can track and sense the quality of the air (and environment!) around you with an **IoT Air Quality Sensor.** This sensor **measures PM2.5 (particles that are 2.5 microns or smaller in diameter) dust concentrations, temperature and humidity**. This sensor is small, wall-mountable (indoors or outdoors), weatherproof, and only requires a WiFi network connection and an AC outlet.

You'll **assemble an open source air quality sensor.** Then, you'll **program the sensor using CircuitPython** to measure air quality data and periodically **send measurements to [Adafruit IO, our incredible IoT Service](http://io.adafruit.com/welcome).** Finally, you'll create a beautiful Adafruit IO dashboard to **visualize your sensor data from anywhere in the world**.

![temperature___humidity_3BFCF078-1DC8-4F52-9AEA-E81B5C605BA1_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/871/medium640/temperature___humidity_3BFCF078-1DC8-4F52-9AEA-E81B5C605BA1_1_105_c.jpeg?1602875174)

![](https://cdn-learn.adafruit.com/assets/assets/000/095/872/medium800/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602790607)

## Why would I want to build an Air Quality Monitor?
### Citizen Science

Soon after the Fukushima nuclear disaster in Japan, trustworthy information about radiation levels was publicly unavailable. An international volunteer organization, [Safecast](https://safecast.org/about/), designed devices for radiation mapping and openly shared their measurements to the public.&nbsp;

With the increasing amount of natural disasters, **building an open source air quality monitor is a step towards citizens being able to monitor and share data about essential environmental measurements without the need to trust an environmental regulatory body or wait for an official government response**.&nbsp;

![temperature___humidity_safecast.png](https://cdn-learn.adafruit.com/assets/assets/000/095/706/medium640/temperature___humidity_safecast.png?1602601396)

### Open Source Science

While building this sensor, we noticed a large amount of the current air quality sensor offerings on the market are closed source software and have a private API. This means it's impossible to send data from a DIY air quality sensor to their web service. If we purchased one of the company sponsored sensors and the company went out of business, we'd be left with a sensor which couldn't send data to the internet.

Building an own open source air quality monitoring sensor lets you control the hardware going into your IoT air quality sensor, the software running on the sensor (right down to the firmware!) and the web platform. We are using [Adafruit.io](http://io.adafruit.com/welcome) for this guide, but you are free to modify the code to send data to other services such as Google Cloud Platform, Amazon AWS IoT, or Microsoft Azure IoT.

## Parts
Featured
### Adafruit Feather M4 Express - Featuring ATSAMD51

[Adafruit Feather M4 Express - Featuring ATSAMD51](https://www.adafruit.com/product/3857)
It's what you've been waiting for, the Feather M4 Express featuring ATSAMD51. This Feather is fast like a swift, smart like an owl, strong like a ox-bird (it's half ox, half bird, OK?) This feather is powered by our new favorite chip, the **ATSAMD51J19** -&nbsp; with...

In Stock
[Buy Now](https://www.adafruit.com/product/3857)
[Related Guides to the Product](https://learn.adafruit.com/products/3857/guides)
![Angled shot of a Adafruit Feather M4 Express. ](https://cdn-shop.adafruit.com/640x480/3857-10.jpg)

Featured
### Adafruit AirLift FeatherWing – ESP32 WiFi Co-Processor

[Adafruit AirLift FeatherWing – ESP32 WiFi Co-Processor](https://www.adafruit.com/product/4264)
Give your Feather project a _lift_ with the Adafruit AirLift FeatherWing - a FeatherWing that lets you use the powerful ESP32 as a WiFi co-processor. You probably have your favorite Feather ([like the Feather M4](https://www.adafruit.com/product/3857)) that comes with its own...

In Stock
[Buy Now](https://www.adafruit.com/product/4264)
[Related Guides to the Product](https://learn.adafruit.com/products/4264/guides)
![Angled shot of Adafruit AirLift FeatherWing.](https://cdn-shop.adafruit.com/640x480/4264-07.jpg)

Featured
### PM2.5 Air Quality Sensor and Breadboard Adapter Kit

[PM2.5 Air Quality Sensor and Breadboard Adapter Kit](https://www.adafruit.com/product/3686)
Breathe easy, knowing that you can track and sense the quality of the air around you with th **e PM2.5&nbsp;Air Quality Sensor with Breadboard Adapter** particulate sensor. [Mad Max & Furiosa definitely should have hooked up one of...](https://en.wikipedia.org/wiki/Mad_Max)

In Stock
[Buy Now](https://www.adafruit.com/product/3686)
[Related Guides to the Product](https://learn.adafruit.com/products/3686/guides)
![Person soldering next to sensor, which detects the fumes and lights up an LED red](https://cdn-shop.adafruit.com/product-videos/640x480/3686-04.jpg)

Featured
### Adafruit BME280 I2C or SPI Temperature Humidity Pressure Sensor

[Adafruit BME280 I2C or SPI Temperature Humidity Pressure Sensor](https://www.adafruit.com/product/2652)
Bosch has stepped up their game with their new BME280 sensor, an environmental sensor with temperature, barometric pressure&nbsp;and&nbsp;humidity! This sensor is great for all sorts of indoor environmental sensing and can even be used in both I2C and SPI!

This precision sensor from...

In Stock
[Buy Now](https://www.adafruit.com/product/2652)
[Related Guides to the Product](https://learn.adafruit.com/products/2652/guides)
![small, rectangle-shaped, BME280 temperature humidity pressure sensor breakout board.](https://cdn-shop.adafruit.com/640x480/2652-04.jpg)

Featured
### FeatherWing Doubler - Prototyping Add-on For All Feather Boards

[FeatherWing Doubler - Prototyping Add-on For All Feather Boards](https://www.adafruit.com/product/2890)
This is the **FeatherWing Doubler** - a prototyping add-on and more for all Feather boards. This is similar to our [FeatherWing Proto](https://www.adafruit.com/products/2884) except there are two! The magic of the Doubler comes when stacking a Feather and another...

In Stock
[Buy Now](https://www.adafruit.com/product/2890)
[Related Guides to the Product](https://learn.adafruit.com/products/2890/guides)
![Double prototyping feather wing PCB with socket headers installed](https://cdn-shop.adafruit.com/640x480/2890-01.jpg)

Featured
### Flanged Weatherproof Enclosure With PG-7 Cable Glands

[Flanged Weatherproof Enclosure With PG-7 Cable Glands](https://www.adafruit.com/product/3931)
Whether you're raiding tombs or traversing nuclear fallout wastelands, this is the most heavy-duty enclosure for your project! Weatherproof? Check. Tough&nbsp;polycarbonate cover? Check. Built-in gasket seal? Oh yeah, check.

Store your project safe and sound in this **Flanged...**

In Stock
[Buy Now](https://www.adafruit.com/product/3931)
[Related Guides to the Product](https://learn.adafruit.com/products/3931/guides)
![Angled shot of off-white, rectangle-shaped, flanged enclosure.](https://cdn-shop.adafruit.com/640x480/3931-00.jpg)

### Silicone Cover Stranded-Core Ribbon Cable - 4 Wires 1 Meter Long

[Silicone Cover Stranded-Core Ribbon Cable - 4 Wires 1 Meter Long](https://www.adafruit.com/product/3889)
For those who are fans of our silicone-covered wires, but are always looking to _up their wiring game_. We now have **Silicone Cover Ribbon cables!** These may look _a lot_ like <a...></a...>

In Stock
[Buy Now](https://www.adafruit.com/product/3889)
[Related Guides to the Product](https://learn.adafruit.com/products/3889/guides)
![A coiled Silicone Cover Stranded-Core Ribbon Cable - 4 Wires 1 Meter Long](https://cdn-shop.adafruit.com/640x480/3889-01.jpg)

### Lithium Ion Polymer Battery Ideal For Feathers - 3.7V 400mAh

[Lithium Ion Polymer Battery Ideal For Feathers - 3.7V 400mAh](https://www.adafruit.com/product/3898)
Lithium-ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light, and powerful. The output ranges from 4.2V when completely charged to 3.7V. This battery has a capacity of **400mAh** for a total of about 1.9 Wh. If you need a larger (or smaller!)...

In Stock
[Buy Now](https://www.adafruit.com/product/3898)
[Related Guides to the Product](https://learn.adafruit.com/products/3898/guides)
![Slim Lithium Ion Polymer Battery 3.7v 400mAh with JST 2-PH connector and short cable](https://cdn-shop.adafruit.com/640x480/3898-05.jpg)

### 5V 2A Switching Power Supply w/ USB-A Connector

[5V 2A Switching Power Supply w/ USB-A Connector](https://www.adafruit.com/product/1994)
Our 5V 2A USB power adapter is the perfect choice for powering single-board computers like Raspberry Pi, BeagleBone, or anything else that's power-hungry!

This adapter was specifically designed to provide 5.25V, not 5V, but we still call it a 5V USB adapter. We did this on purpose to...

In Stock
[Buy Now](https://www.adafruit.com/product/1994)
[Related Guides to the Product](https://learn.adafruit.com/products/1994/guides)
![5V 2A Switching Power Supply with  USB-A Connector](https://cdn-shop.adafruit.com/640x480/1994-01.jpg)

### USB A/Micro Cable - 2m

[USB A/Micro Cable - 2m](https://www.adafruit.com/product/2185)
This is your standard USB A-Plug&nbsp;to Micro-USB cable. It's 2 meters long so you'll have plenty of cord to work with for those longer extensions.

Out of Stock
[Buy Now](https://www.adafruit.com/product/2185)
[Related Guides to the Product](https://learn.adafruit.com/products/2185/guides)
![USB Cable with Type A and Micro B ends](https://cdn-shop.adafruit.com/640x480/2185-00.jpg)

# IoT Air Quality Sensor with Adafruit IO

## Adafruit IO Setup

## Obtain Adafruit IO Key

You will need your Adafruit IO username and secret API key.

[**Navigate to Adafruit IO**](https://io.adafruit.com/profile) and **click the Adafruit IO Key button** to retrieve these values. Write them down in a safe place, you'll need them later.

## Create Group

This guide will use multiple Adafruit IO feeds to store sensor values. To organize these feeds, you will need to create a new group.&nbsp;

 **Navigate** to [your Adafruit IO Feeds page](https://io.adafruit.com/feeds).

**Click** _Actions -\> Create a New Group_

**Name the group** _Air Quality Sensor_. You can optionally set a description.

**Click Create**

![temperature___humidity_create_new_group.png](https://cdn-learn.adafruit.com/assets/assets/000/095/444/medium640/temperature___humidity_create_new_group.png?1602101870)

![temperature___humidity_name_group.png](https://cdn-learn.adafruit.com/assets/assets/000/095/445/medium640/temperature___humidity_name_group.png?1602101902)

## Add Feeds to Group

Lets add a few feeds to the Air Quality Sensor group to hold sensor measurements and metadata.&nbsp;

 **Click** _Actions -\> Create a New Feed_

**Name the feed** _AQI_

**Click** _Add to Groups&nbsp;_and&nbsp; **select&nbsp;the Air Quality Sensor group**

**Click Create**

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/448/medium640/temperature___humidity_IO_-_Feeds.png?1602102614)

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/449/medium640/temperature___humidity_IO_-_Feeds.png?1602102712)

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/450/medium640/temperature___humidity_IO_-_Feeds.png?1602102824)

Repeat the process in the step above to **create feeds for category (AQI category), temperature, and humidity**.

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/451/medium640/temperature___humidity_IO_-_Feeds.png?1602102929)

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/452/medium640/temperature___humidity_IO_-_Feeds.png?1602102963)

![temperature___humidity_IO_-_Feeds.png](https://cdn-learn.adafruit.com/assets/assets/000/095/453/medium640/temperature___humidity_IO_-_Feeds.png?1602102997)

Before proceeding, make sure your Air Quality Sensor group looks exactly like the screenshot below.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/454/medium800/temperature___humidity_IO_-_Feeds.png?1602103078)

## Adafruit IO Dashboard

Dashboards allow you to visualize data and control Adafruit IO connected projects from any modern web browser. We'll be adding gauge widgets to visualize data from the air quality sensor in real-time and charts to display data historically.

Navigate to&nbsp;[the dashboards page on Adafruit IO](https://io.adafruit.com/dashboards).&nbsp;

**Click Actions -\> Create New Dashboard**

**Name the dashboard** My Air Quality Sensor&nbsp;

**Click Create&nbsp;**

![temperature___humidity_IO_-_Dashboards.png](https://cdn-learn.adafruit.com/assets/assets/000/095/457/medium640/temperature___humidity_IO_-_Dashboards.png?1602104346)

![temperature___humidity_IO_-_Dashboards.png](https://cdn-learn.adafruit.com/assets/assets/000/095/458/medium640/temperature___humidity_IO_-_Dashboards.png?1602104370)

You should see your new dashboard pop-up in the list of Dashboards. **Click the My Air Quality Sensor dashboard link** to navigate to the dashboard page.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/460/medium800/temperature___humidity_IO_-_Dashboards.png?1602104592)

You should see an empty dashboard. Let's fill it with blocks!

**Click the '+' button on your dashboard to add a new block**.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/462/medium800/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602104632)

Let's add a text block to display the air quality condition (Good, Acceptable, Moderate, etc) sent by the sensor.

From the Create a New Block picker, **click the Text Block**

From the Create a New Block picker,&nbsp; **click the Text Block**

On the Choose Feed picker, **select the category feed** &nbsp;

Under Block Settings:

- **Set Block Title** to AQI Category
- **Set Font Size** to Large
- **Click** Create Block

![temperature___humidity_IO_-_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/464/medium640/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602104881)

![temperature___humidity_IO_-_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/465/medium640/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602104960)

![temperature___humidity_IO_-_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/468/medium640/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602105040)

You should see the AQI Category text box appear on the dashboard. We'll organize the dashboard last - let's add the next block.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/470/medium800/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602105255)

## Add Gauge Block for Real-Time AQI

The United States Environmental Protection Agency [uses an Air Quality Index (AQI) to communicate air quality.](https://www.airnow.gov/aqi/) While computing the AQI according to the EPA requires a 24 hour average, this gauge displays the real-time AQI.

- **Note:** This guide uses air quality breakpoints and conditions developed by the USA EPA. Other countries have environmental protection agencies with similar countries with air quality indexes using PM2.5 particles. [Check out this Wikipedia page for more info](https://en.wikipedia.org/wiki/Air_quality_index#Indices_by_location).

 **Click** Create a New Block

**Select&nbsp;** the Gauge Block

**Select** the _aqi_ feed

Under Block Settings:

- **Set Block Title** to _Real-Time AQI_
- **Set the Gauge Max Value** to 500
- **Set Gauge Width** to 50px
- **Remove the "Value" text placeholder**  **from Gauge Label** , AQI is a unit-less value.
- **Set High Warning Value to 151**
  - [This AQI value corresponds with the EPA's "unhealthy" AQI category](https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI).

- **Set Decimal Places** to **0**
- **Tick the Show Icon checkbox**
- **Set Icon&nbsp;** to&nbsp; **w:cloudy-windy**
- **Click Create Block**

![temperature___humidity_create_new_block_gauge_1.png](https://cdn-learn.adafruit.com/assets/assets/000/095/472/medium640/temperature___humidity_create_new_block_gauge_1.png?1602106558)

![temperature___humidity_create_new_gauge_2.png](https://cdn-learn.adafruit.com/assets/assets/000/095/473/medium640/temperature___humidity_create_new_gauge_2.png?1602106576)

![temperature___humidity_IO_-_My_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/483/medium640/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602163902)

Your dashboard should now show the AQI category text block and the Real-Time AQI gauge.

## Add Gauge Block for Humidity

We'll add another gauge block to display the BME280's humidity reading.

 **Click** &nbsp;Create a New Block

**Select&nbsp;** the Gauge Block

**Select** &nbsp;the _humidity_ feed

Under Block Settings:

- **Set Block Title** &nbsp;to&nbsp;_Current Humidity_
- **Set Gauge Width** &nbsp;to 50px
- **Remove the "Value" text placeholder** &nbsp; **from Gauge Label**
- **Tick the Show Icon checkbox**
- **Set Icon&nbsp;** to&nbsp; **w:humidity**
- **Click Create Block**

![temperature___humidity_create_new_block_gauge_1.png](https://cdn-learn.adafruit.com/assets/assets/000/095/475/medium640/temperature___humidity_create_new_block_gauge_1.png?1602106960)

![temperature___humidity_IO_-_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/476/medium640/temperature___humidity_IO_-_Air_Quality_Sensor.png?1602106996)

![temperature___humidity_IO_-_My_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/477/medium640/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602107111)

## Add Gauge Block for Temperature

We'll add another gauge block to display the BME280's temperature reading.

 **Click** &nbsp; **Create a New Block**

**Select&nbsp;** the **Gauge**  **Block**

**Select** &nbsp;the&nbsp;_humidity_&nbsp;feed

Under Block Settings:

- **Set Block Title** &nbsp;to&nbsp;_Current Temperature_
- **Set Gauge Width** &nbsp;to 50px
- **Set the Gauge label** to **Degrees F or Degrees C**
- **Click the Show Icon checkbox**
- **Set Icon&nbsp;** to&nbsp; **thermometer**
- **Click Create Block**

![temperature___humidity_create_new_block_gauge_1.png](https://cdn-learn.adafruit.com/assets/assets/000/095/478/medium640/temperature___humidity_create_new_block_gauge_1.png?1602107268)

![temperature___humidity_IO_-_My_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/479/medium640/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602107373)

![temperature___humidity_IO_-_My_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/480/medium640/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602107492)

## Add Line Charts

While real-time visualization of PM2.5 measurements over time is immediately useful - looking at the air quality index over time will help you understand the AQI as a more accurate average. Adafruit IO's Line Charts update dynamically whenever new values are pushed to the feed.

Since most environmental groups use a 24-hour average of AQI measurements, we'll create a new line chart block to display the AQI measurements for the past day.

 **Click**  **Create a New Block**

**Select** the **Line Chart Block**

**Select** the _aqi_ **feed**

Under Block Settings:

- **Set Block Title** to **AQI - 24 Hours**
- **Set Show History&nbsp;** to **24 Hours**
- Click the **Draw Grid Lines** checkbox

**Click Create Block**

![temperature___humidity_line_block_1.png](https://cdn-learn.adafruit.com/assets/assets/000/095/485/medium640/temperature___humidity_line_block_1.png?1602164986)

![temperature___humidity_line_chart_2.png](https://cdn-learn.adafruit.com/assets/assets/000/095/486/medium640/temperature___humidity_line_chart_2.png?1602164998)

![temperature___humidity_line_chart_3.png](https://cdn-learn.adafruit.com/assets/assets/000/095/487/medium640/temperature___humidity_line_chart_3.png?1602165021)

Let's make another line chart block to display the AQI&nbsp; from the past week.

 **Click** &nbsp; **Create a New Block**

**Select** &nbsp;the **&nbsp;Line Chart Block**

**Select** &nbsp;the&nbsp;_aqi_&nbsp; **feed**

Under Block Settings:

- **Set Block Title** &nbsp;to&nbsp; **AQI - 24 hours**
- **Set Show History&nbsp;** to **7 Days**
- Click the **&nbsp;Draw Grid Lines** &nbsp;checkbox

**Click Create Block**

![temperature___humidity_line_block_1.png](https://cdn-learn.adafruit.com/assets/assets/000/095/510/medium640/temperature___humidity_line_block_1.png?1602188870)

![temperature___humidity_line_chart_2.png](https://cdn-learn.adafruit.com/assets/assets/000/095/511/medium640/temperature___humidity_line_chart_2.png?1602188877)

![temperature___humidity_IO_-_My_Air_Quality_Sensor.png](https://cdn-learn.adafruit.com/assets/assets/000/095/512/medium640/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602188896)

## Organize Dashboard
You can drag the dashboard blocks around to re-organize your dashboard.&nbsp;

Before moving on, make sure your dashboard contains the same blocks as the screenshot below

![](https://cdn-learn.adafruit.com/assets/assets/000/095/513/medium800/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602189022)

# IoT Air Quality Sensor with Adafruit IO

## Wiring

## Attach Cables to BME280 and PMS5003 Adaptor
The PMS5003 comes with a breadboard adapter for you to easily hook the sensor up to a microcontroller with UART.

While you could use a female/female header adaptor, wires can easily disconnect. You'll want to solder cables from the FeatherWing Doubler to the breadboard adaptor for a stronger hold.

Let's begin by removing the pins from the breadboard adaptor.

![temperature___humidity_pm25_tweeze.png](https://cdn-learn.adafruit.com/assets/assets/000/095/720/medium640/temperature___humidity_pm25_tweeze.png?1602607333)

Place the breadboard adaptor into a [vise](https://www.adafruit.com/product/151) and flip the board over.

![temperature___humidity_36604608-F979-4C52-A18A-FDF7902236AF_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/722/medium640/temperature___humidity_36604608-F979-4C52-A18A-FDF7902236AF_1_105_c.jpeg?1602607435)

![temperature___humidity_1E9F208E-B826-463B-A6F0-9E7A37716BBC_1_201_a.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/723/medium640/temperature___humidity_1E9F208E-B826-463B-A6F0-9E7A37716BBC_1_201_a.jpeg?1602607509)

Using a pair of [flush diagonal cutters](https://www.adafruit.com/product/152), snip between the pins of the plastic header. Make sure to **snip the plastic between each pin**. This will make it easier to pull the pins from the adaptor when you de-solder them.

![temperature___humidity_7AEEB356-59C9-4C67-A100-DA0C1A8766D4_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/724/medium640/temperature___humidity_7AEEB356-59C9-4C67-A100-DA0C1A8766D4_1_105_c.jpeg?1602607550)

![temperature___humidity_F2AD9B01-E7CB-4BC4-9C90-8A0A8124FD17_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/725/medium640/temperature___humidity_F2AD9B01-E7CB-4BC4-9C90-8A0A8124FD17_1_105_c.jpeg?1602607634)

![temperature___humidity_E87C865F-DEB2-49E7-8897-DD4D60DC1E53_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/726/medium640/temperature___humidity_E87C865F-DEB2-49E7-8897-DD4D60DC1E53_1_105_c.jpeg?1602607676)

 **Tin the tip of your soldering iron**. Then, **press the edge of your soldering iron against a solder-blob** on the adaptor to heat and liquify the solder.

While **heating the pin, use a pair of tweezers (or needle-nose pliers), wiggle the pin out of the hole in the PCB until it's removed**.

![temperature___humidity_desolder.gif](https://cdn-learn.adafruit.com/assets/assets/000/095/742/medium640thumb/temperature___humidity_desolder.jpg?1602618535)

Using desoldering wick or a desoldering pump, r **emove old/excess solder from the adaptor**.

![temperature___humidity_desolder_pump.gif](https://cdn-learn.adafruit.com/assets/assets/000/095/743/medium640thumb/temperature___humidity_desolder_pump.jpg?1602618996)

![temperature___humidity_4517895A-EDC3-4DED-ACBC-156A4D581EB5_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/744/medium640/temperature___humidity_4517895A-EDC3-4DED-ACBC-156A4D581EB5_1_105_c.jpeg?1602619076)

 **Cut a long piece of silicone stranded-core ribbon cable from the 1M of cable.** It's always better to have more cable then less!

Then, **peel four cables off the silicone stranded-core ribbon cable.** &nbsp; **Strip a small amount of the silicone sheath from each wire and tin the end of it**.

![temperature___humidity_ED9E4B54-AA01-4292-BBD2-F10CB158E0D6_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/728/medium640/temperature___humidity_ED9E4B54-AA01-4292-BBD2-F10CB158E0D6_1_105_c.jpeg?1602607764)

![temperature___humidity_5758A3A8-7A1D-4D8A-A392-B91967EF17ED_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/729/medium640/temperature___humidity_5758A3A8-7A1D-4D8A-A392-B91967EF17ED_1_105_c.jpeg?1602607799)

 **Solder a length of wire to each of the VCC, GND, TXD, and RST pins on the adaptor.** Your final adaptor should look like the following:

![](https://cdn-learn.adafruit.com/assets/assets/000/095/730/medium800/temperature___humidity_E454169C-3859-4EEF-A730-D39AEA388CB0_1_105_c.jpeg?1602607952)

Cut a new length of silicone stranded-core ribbon cable and **repeat this process for the BME280** breakout. Both breakouts should now have cables attached.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/731/medium800/temperature___humidity_FA2A8C86-4EE2-495E-8EA8-358F764E266A_1_201_a.jpeg?1602608031)

## Wiring

Next, you'll need to connect the BME280 and PM2.5 adaptor to the FeatherWing Doubler. We suggest using one of the two prototyping spots on the front of the FeatherWing Doubler.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/750/medium800/temperature___humidity_wiring_bb.png?1602620625)

 **Make the following connections between the BME280 and the FeatherWing:**

- **Board 3V** &nbsp;to&nbsp; **sensor VIN**
- **Board 3V** &nbsp;to&nbsp; **sensor CS**
- **Board GND** &nbsp;to&nbsp; **sensor GND**
- **Board SCL** &nbsp;to&nbsp; **sensor SCK**
- **Board SDA** &nbsp;to&nbsp; **sensor SDI**

**Then, make the following connections between the PM2.5 adaptor and the FeatherWing:**

- **Sensor VCC&nbsp;** to **&nbsp;board 5V**
- **Sensor GND&nbsp;** to **&nbsp;board GND**
- **Sensor TX&nbsp;** to **&nbsp;board RX**
  - Remember:&nbsp; **RX does not connect to RX!**

After making the connections above, your FeatherWing should look like the following.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/755/medium800/temperature___humidity_67480A70-DDB2-4A53-A3EF-8F40C28EA7F0_1_105_c.jpeg?1602622261)

Let's move on to assembling the enclosure.

# IoT Air Quality Sensor with Adafruit IO

## Assembly

## Assemble the FeatherWing Doubler
Plug the Feather M4 into the FeatherWing Doubler.

Then, plug the AirLift FeatherWing into the FeatherWing Doubler

![temperature___humidity_2798F888-4E11-424C-BDB7-C45487B9361E_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/753/medium640/temperature___humidity_2798F888-4E11-424C-BDB7-C45487B9361E_1_105_c.jpeg?1602622158)

![temperature___humidity_5319DBA0-19B9-42FF-8F2E-C5279950B1ED_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/792/medium640/temperature___humidity_5319DBA0-19B9-42FF-8F2E-C5279950B1ED_1_105_c.jpeg?1602687493)

Plug the PM25 sensor into the adaptor breakout.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/793/medium800/temperature___humidity_BD29F372-1748-46E8-8424-8385E8DFFD88_1_105_c.jpeg?1602687511)

## Test-Fit Enclosure

Next, you'll want to make sure the enclosure fits the hardware you assembled and check the lengths of the wires you soldered.

Using a phillips head screwdriver, open the enclosure.

![temperature___humidity_34DDCB6F-DF39-46E6-90FF-3F357491AB9B_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/794/medium640/temperature___humidity_34DDCB6F-DF39-46E6-90FF-3F357491AB9B_1_105_c.jpeg?1602687779)

![temperature___humidity_308A3E8A-F74C-4109-8D0C-3EECB5FB87A3_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/795/medium640/temperature___humidity_308A3E8A-F74C-4109-8D0C-3EECB5FB87A3_1_105_c.jpeg?1602687789)

![temperature___humidity_1F89E887-D1F6-4304-8588-F103255F1E28_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/796/medium640/temperature___humidity_1F89E887-D1F6-4304-8588-F103255F1E28_1_105_c.jpeg?1602687808)

Place the PM25 sensor inside the enclosure. It should fit between the four mounting standoffs.

Make sure the sensor's fan faces the bottom of the enclosure and the cable faces the top of the enclosure (the side with the cable glands).

![temperature___humidity_B0E654EC-938C-49C6-9470-96801A0002EC_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/797/medium640/temperature___humidity_B0E654EC-938C-49C6-9470-96801A0002EC_1_105_c.jpeg?1602688060)

Place the FeatherWing on top of the PM25 sensor and cover it with the lid.&nbsp;

If you noticed the cables you cut were too long or if the solder joints broke, you'll want to fix them now.

![temperature___humidity_F0B615C6-0220-4322-8112-811CDB498195_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/798/medium640/temperature___humidity_F0B615C6-0220-4322-8112-811CDB498195_1_105_c.jpeg?1602688086)

![temperature___humidity_AA582ADF-C9DB-4E3C-B1DE-A653D5DFE48D_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/799/medium640/temperature___humidity_AA582ADF-C9DB-4E3C-B1DE-A653D5DFE48D_1_105_c.jpeg?1602688112)

![temperature___humidity_39218E8A-C6EC-46A8-AD2F-5C5938C8D620_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/800/medium640/temperature___humidity_39218E8A-C6EC-46A8-AD2F-5C5938C8D620_1_105_c.jpeg?1602688127)

## Add Air-Holes to Enclosure

Next, you will use a handheld drill, a drill press, or a rotary tool to add some holes to the bottom of the enclosure. This will allow air particles to enter the enclosure.&nbsp;

The PM2.5 sensor's fan intake is located on the bottom right hand side of the enclosure. The BME280 sensor will also be placed in the same location.

Using a marker and ruler, **mark a few spots at the _bottom right_ of the enclosure**.

**Use a drill and a small-diameter drill bit to drill holes into the enclosure**. Be sure to **use a slow speed on your drill** , the enclosure's material is softer than it seems and you can accidentally drill through too quickly.

Do NOT add a filter to the bottom of the enclosure - it will prevent dust particles from being picked up by the fan.

![temperature___humidity_E7B9F80D-4CCF-48B1-81A8-E9428CC9C449_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/831/medium640/temperature___humidity_E7B9F80D-4CCF-48B1-81A8-E9428CC9C449_1_105_c.jpeg?1602709148)

![temperature___humidity_F0852B59-8E02-406D-BF34-E938B7FC7A25_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/832/medium640/temperature___humidity_F0852B59-8E02-406D-BF34-E938B7FC7A25_1_105_c.jpeg?1602709160)

Using an adhesive of your choice - **affix the PM2.5 sensor to the bottom of the case.**

We used 3M Command Strips for a more temporary hold. If you're also using Command Strips - be sure to place the wall (black) side against the enclosure.

![temperature___humidity_EDBA84B6-EA76-4C69-9F04-BA78705E2805_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/835/medium640/temperature___humidity_EDBA84B6-EA76-4C69-9F04-BA78705E2805_1_105_c.jpeg?1602709247)

![temperature___humidity_1829B72C-0764-4992-814F-49C17A3486DD_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/836/medium640/temperature___humidity_1829B72C-0764-4992-814F-49C17A3486DD_1_105_c.jpeg?1602709267)

![temperature___humidity_98FB2CD8-3164-4F23-9948-56A6ED922917_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/838/medium640/temperature___humidity_98FB2CD8-3164-4F23-9948-56A6ED922917_1_105_c.jpeg?1602709311)

![temperature___humidity_2A20C6B5-A034-46E7-A8FF-30746ED15F4C_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/839/medium640/temperature___humidity_2A20C6B5-A034-46E7-A8FF-30746ED15F4C_1_105_c.jpeg?1602709328)

Cut a Micro-USB cable in half with a pair of wire cutters.

Thread the Micro-USB end through the right cable gland.

You'll need to strip and splice together&nbsp; the two ends of the USB cable. [For more information about splicing wires, see this Learning System Guide](https://learn.adafruit.com/make-it-glow-how-to-solder-neopixels-a-beginners-guide/splicing-wires).

![temperature___humidity_1710E2EE-CE9E-44AF-92C2-953736E409ED_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/864/medium640/temperature___humidity_1710E2EE-CE9E-44AF-92C2-953736E409ED_1_105_c.jpeg?1602789622)

![temperature___humidity_0A6CAEA7-6DA0-4AC3-9DAD-3BBA210D03C3_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/865/medium640/temperature___humidity_0A6CAEA7-6DA0-4AC3-9DAD-3BBA210D03C3_1_105_c.jpeg?1602789634)

![temperature___humidity_83938003-78DF-4BF4-9A97-5C518B524D6B_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/866/medium640/temperature___humidity_83938003-78DF-4BF4-9A97-5C518B524D6B_1_105_c.jpeg?1602789653)

Add two adhesive strips to each side of the FeatherWing Doubler and insert the USB cable into the Feather M4.

You may need to re-organize cables at this point. We stored all of the cables in the top section in the case. **Then, press the FeatherWing Doubler onto the PM2.5 sensor**.

![temperature___humidity_66F39881-5A85-44A2-8031-E580B2B13D99_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/840/medium640/temperature___humidity_66F39881-5A85-44A2-8031-E580B2B13D99_1_105_c.jpeg?1602709354)

![temperature___humidity_848A8610-5616-477E-9F62-3C964128C0F3_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/841/medium640/temperature___humidity_848A8610-5616-477E-9F62-3C964128C0F3_1_105_c.jpeg?1602709370)

 **Tighten the cable gland to form a weatherproof seal around the USB cable**. Make sure the left cable gland is also tightened.&nbsp;

![temperature___humidity_17FAAAD8-8CDF-4A3B-AD15-A79870E84CDE_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/842/medium640/temperature___humidity_17FAAAD8-8CDF-4A3B-AD15-A79870E84CDE_1_105_c.jpeg?1602709399)

 **Place the transparent cover over the enclosure** , ensuring the gasket which runs around the enclosure is properly sealed.

**Secure the cover to the enclosure using the four machine screws** included with the enclosure.

![temperature___humidity_1BF3A38C-717D-4F0B-AF78-7CC196C14077_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/843/medium640/temperature___humidity_1BF3A38C-717D-4F0B-AF78-7CC196C14077_1_105_c.jpeg?1602709431)

![temperature___humidity_3A8CB806-1FB4-4C37-A5BC-B9B4DD4F9261_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/847/medium640/temperature___humidity_3A8CB806-1FB4-4C37-A5BC-B9B4DD4F9261_1_105_c.jpeg?1602711624)

That's it! Your air quality sensor is assembled and wired. The next steps will cover adding code to the Feather M4 and using the air quality sensor with Adafruit IO.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/877/medium800/temperature___humidity_9148FA45-CD3B-4693-9F45-FE2317C6B5EF_1_105_c.jpeg?1602794093)

# IoT Air Quality Sensor with Adafruit IO

## CircuitPython Setup

## Install CircuitPython

Some CircuitPython compatible boards come with CircuitPython installed. Others are&nbsp;_CircuitPython-ready_, but need to have it installed. As well, you may want to update the version of CircuitPython already installed on your board. The steps are the same for installing and updating.&nbsp;

- To&nbsp;**install (or update) your CircuitPython board**,&nbsp;[follow this page and come back here when you've successfully installed (or updated) CircuitPython.](https://learn.adafruit.com/welcome-to-circuitpython/installing-circuitpython)

## CircuitPython Library Installation

First make sure you are running the&nbsp;[latest version of Adafruit CircuitPython](https://github.com/adafruit/circuitpython/releases)&nbsp;for your board.

Next you'll need to install the necessary libraries&nbsp;to use the hardware--carefully follow the steps to find and install these libraries from&nbsp;[Adafruit's CircuitPython library bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle)&nbsp;matching your version of CircuitPython.&nbsp;

CircuitPython hardware shows up on your computer operating system as a flash drive when connected via usb. The flash drive is called&nbsp; **CIRCUITPY** &nbsp;and contains a number of files. You will need to add additional files to enable the features of this project.

First, create a folder on the drive named lib if it is not already there.

Ensure your board's&nbsp; **lib** &nbsp;folder has the following files and folders **&nbsp;** copied over. The version of the files must be the same major version as your version of CircuitPython (i.e. 5.x for 5.x, 6.x for 6.x etc.)

- **adafruit\_bus\_device**
- **adafruit\_esp32spi**
- **adafruit\_io**
- **adafruit\_logging.mpy**
- **adafruit\_requests.mpy**
- **adafruit\_pm25**
- **neopixel.mpy**
- **simpleio.mpy**
- **adafruit\_bme280.mpy**

# IoT Air Quality Sensor with Adafruit IO

## Internet Connect!

# Connect to WiFi

OK, now that you have your&nbsp; **settings.toml** file set up - you can connect to the Internet.

To do this, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **examples/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_CircuitPython_Bundle/esp32spi_esp32spi_simpletest.py.png )

Info: Update to CircuitPython 9.2.x or later to use this example.

https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI/blob/main/examples/esp32spi_simpletest.py

And save it to your board, with the name **code.py**.

Don't forget you'll also need to create the **settings.toml** file as seen above, with your WiFi ssid and password.

In a serial console, you should see something like the following. For more information about connecting with a serial console, view the guide [Connecting to the Serial Console](https://learn.adafruit.com/welcome-to-circuitpython/kattni-connecting-to-the-serial-console).

```terminal
>>> import wifitest
ESP32 SPI webclient test
ESP32 found and in idle mode
Firmware vers. 1.7.5
MAC addr: 24:C9:DC:BD:0F:3F
	HomeNetwork             RSSI: -46
	HomeNetwork             RSSI: -76
	Fios-12345              RSSI: -92
	FiOS-AB123              RSSI: -92
	NETGEAR53               RSSI: -93
Connecting to AP...
Connected to HomeNetwork 	RSSI: -45
My IP address is 192.168.1.245
IP lookup adafruit.com: 104.20.39.240
Ping google.com: 30 ms
Fetching text from http://wifitest.adafruit.com/testwifi/index.html
----------------------------------------
This is a test of Adafruit WiFi!
If you can read this, its working :)
----------------------------------------

Fetching json from http://wifitest.adafruit.com/testwifi/sample.json
----------------------------------------
{'fun': True, 'company': 'Adafruit', 'founded': 2005, 'primes': [2, 3, 5], 'pi': 3.14, 'mixed': [False, None, 3, True, 2.7, 'cheese']}
----------------------------------------
Done!
```

Going over the example above, here's a breakdown of what the program is doing:

- Initialize the ESP32 over SPI using the SPI port and 3 control pins:

```python
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

#...

else:
    spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
```

- Get the socket pool and the SSL context, and then tell the `adafruit_requests` library about them.

```python
pool = adafruit_connection_manager.get_radio_socketpool(esp)
ssl_context = adafruit_connection_manager.get_radio_ssl_context(esp)
requests = adafruit_requests.Session(pool, ssl_context)
```

- Verify an ESP32 is found, checks the firmware and MAC address

```auto
if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
    print("ESP32 found and in idle mode")
print("Firmware vers.", esp.firmware_version)
print("MAC addr:", ":".join("%02X" % byte for byte in esp.MAC_address))
```

- Perform a scan of all access points it can see and print out the name and signal strength.

```python
for ap in esp.scan_networks():
    print("\t%-23s RSSI: %d" % (ap.ssid, ap.rssi))
```

- Connect to the AP we've defined here, then print out the local IP address. Then attempt to do a domain name lookup and ping google.com to check network connectivity. (Note sometimes the ping fails or takes a while; this isn't a big deal.)

```python
print("Connecting to AP...")
while not esp.is_connected:
    try:
        esp.connect_AP(ssid, password)
    except OSError as e:
        print("could not connect to AP, retrying: ", e)
        continue
print("Connected to", esp.ap_info.ssid, "\tRSSI:", esp.ap_info.rssi)
print("My IP address is", esp.ipv4_address)
print(
    "IP lookup adafruit.com: %s" % esp.pretty_ip(esp.get_host_by_name("adafruit.com"))
)
```

Now we're getting to the really interesting part of the example program. We've written a library for web fetching web data, named [adafruit\_requests](https://github.com/adafruit/Adafruit_CircuitPython_Requests). It is a lot like the regular Python library named [requests](https://requests.readthedocs.io/en/latest/). This library allows you to send HTTP and HTTPS requests easily and provides helpful methods for parsing the response from the server.

- Here is the part of the example program is fetching text data from a URL.

```python
TEXT_URL = "http://wifitest.adafruit.com/testwifi/index.html"  # Further up in the program

# ...

print("Fetching text from", TEXT_URL)
r = requests.get(TEXT_URL)
print('-' * 40)
print(r.text)
print('-' * 40)
r.close()
```

- Finally, here the program is fetching some JSON data. The `adafruit_requests` library will parse the JSON into a Python dictionary whose structure is the same as the structure of the JSON.

```auto
JSON_URL = "http://wifitest.adafruit.com/testwifi/sample.json"   # Further up in the program

# ...

print("Fetching json from", JSON_URL)
r = requests.get(JSON_URL)
print('-' * 40)
print(r.json())
print('-' * 40)
r.close()
```

# Advanced Requests Usage

Want to send custom HTTP headers, parse the response as raw bytes, or handle a response's http status code in your CircuitPython code?

We've written an&nbsp;example to show advanced usage of the requests module below.

To use with CircuitPython, you need to first install a few libraries, into the lib folder on your **CIRCUITPY** drive. Then you need to update **code.py** with the example script.

Thankfully, we can do this in one go. In the example below, click the **Download Project Bundle** button below to download the necessary libraries and the **code.py** file in a zip file. Extract the contents of the zip file, open the directory **examples/** and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your **CIRCUITPY** drive.

https://github.com/adafruit/Adafruit_CircuitPython_Requests/blob/main/examples/esp32spi/requests_esp32spi_advanced.py

Your **CIRCUITPY** drive should now look similar to the following image:

![CIRCUITPY](https://adafruit.github.io/Adafruit_CircuitPython_Bundle/requests_esp32spi_requests_esp32spi_advanced.py.png )

# WiFi Manager

The way the examples above connect to WiFi works but it's a little finicky. Since WiFi is not necessarily so reliable, you may have disconnects and need to reconnect. For more advanced uses, we recommend using the `WiFiManager` class. It will wrap the connection/status/requests loop for you - reconnecting if WiFi drops, resetting the ESP32 if it gets into a bad state, etc.

Here's a more advanced example that shows using the `WiFiManager` and also how to fetch the current time from a web source.

https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI/blob/main/examples/esp32spi_localtime.py

# Further Information

For more information on the basics of doing networking in CircuitPython, see this guide:

### Networking in CircuitPython

[Networking in CircuitPython](https://learn.adafruit.com/networking-in-circuitpython)
# IoT Air Quality Sensor with Adafruit IO

## Code Usage

## Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in&nbsp;[this guide](https://learn.adafruit.com/welcome-to-circuitpython/installing-mu-editor).

Alternatively, you can use any text editor that saves simple text files.

## Settings File Setup

Open the **settings.toml** file on your CircuitPython device using Mu or your favorite text editor. You're going to edit this file to enter your WiFi credentials along with your keys.&nbsp;

- Replace `"your-wifi-ssid"` with **your Wi-Fi SSID**
- Replace `"your-wifi-password"` with **your Wi-Fi password**
- Replace `"your-aio-username"` with **your Adafruit IO username**
- Replace `"your-super-long-aio-key"` with **your Adafruit IO key**

```python
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it

CIRCUITPY_WIFI_SSID="your-wifi-ssid"
CIRCUITPY_WIFI_PASSWORD="your-wifi-password"
ADAFRUIT_AIO_USERNAME="your-aio-username"
ADAFRUIT_AIO_KEY="your-super-long-aio-key"
timezone="America/New_York" # http://worldtimeapi.org/timezones
latitude=MY_LAT # https://www.latlong.net/
longitude=MY_LON
elevation=MY_ELE
```

Next, let's to add your location's latitude, longitude and altitude data to the settings file. Entering your location will allow the Map Block to show an image of your sensor's location.

**For privacy reasons, we suggest limiting your location data to your city, town, or municipality**. Instead of setting our sensor's location to Adafruit's exact address, we'll set it to New York City.

[Navigate to this website to find your location's GPS latitude, longitude and altitude coordinates](https://www.maps.ie/coordinates.html) and enter your city/town.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/575/medium800/temperature___humidity_GPS_coordinates_on_Google_Maps___Latitude_Longitude_Search.png?1602277905)

In the settings file, change `MY_LAT`, `MY_LON`, and `MY_ELE` to the values obtained from the website above.

## Code

Click the Download: Project Zip File link below in the code window to get a zip file with all the files needed for the project. Copy&nbsp; **code.py** &nbsp;from the zip file and place on the&nbsp; **CIRCUITPY** &nbsp;drive.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Adafruit_IO_Air_Quality/code.py

Once all the files are copied from your computer to the Feather, you should have the following files on your&nbsp; **CIRCUITPY** &nbsp;drive:

![](https://cdn-learn.adafruit.com/assets/assets/000/095/880/medium800/temperature___humidity_Screen_Shot_2020-10-15_at_4.45.23_PM.png?1602794741)

## Code Usage

Before permanently installing the sensor, you should test the sensor to make sure the sensors are wired correctly and the board can publish data to Adafruit IO.

 **Plug the sensor into a mini-USB power cable**. and **navigate to the Adafruit IO Dashboard you created earlier**. Every ten minutes all the blocks populate with values.

Since the air quality index values are measured in real-time, they may be higher than EPA NowCast real-time AQI values. After a day of the sensor capturing and logging data, the AQI - 1 Day line chart block will display air quality measurements every hour for the previous day.

![](https://cdn-learn.adafruit.com/assets/assets/000/095/826/medium800/temperature___humidity_IO_-_My_Air_Quality_Sensor.png?1602705922)

## Install Sensor

Before deploying your air quality monitor, make sure there's a WiFi network in the location you're planning on deploying to. If you're unsure about connectivity - stand exactly where you want to install the sensor, open your mobile phone/tablet, connect to your WiFi network and navigate to [Adafruit IO](http://io.adafruit.com/) with your web browser.

If the test above was successful, let's move on to installing the sensor. The mounting technique you may use for this sensor varies by installation type.

### Indoor Mounting

If you want to mount your sensor indoors, we suggest using 3M Command Strips.

Peel the plastic backing off a command strip and affix it to the back of the enclosure.&nbsp;Attach a _second_ command strip to the wall.

Press the enclosure into the wall and hold it for a few seconds.

![temperature___humidity_3EB64DA1-4097-42FF-979E-A6F8DB4E1F40_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/819/medium640/temperature___humidity_3EB64DA1-4097-42FF-979E-A6F8DB4E1F40_1_105_c.jpeg?1602692998)

![temperature___humidity_6C89E6A7-98D0-419C-8257-2CC83EE4269E_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/820/medium640/temperature___humidity_6C89E6A7-98D0-419C-8257-2CC83EE4269E_1_105_c.jpeg?1602693016)

![](https://cdn-learn.adafruit.com/assets/assets/000/095/868/medium800/temperature___humidity_55FF9D0F-EBA4-4F4B-A27D-A0FBEDD4F396_1_105_c.jpeg?1602790311)

## Outdoor Mounting

You can also install this sensor outdoors. Make sure you have a WiFi connection and A/C power available.

The flanged weatherproof enclosure has four mounting holes with a 6mm diameter. You can pick up the appropriate screws from your local hardware store's website.

![temperature___humidity_Pasted_Image_10_14_20__12_31_PM.png](https://cdn-learn.adafruit.com/assets/assets/000/095/821/medium640/temperature___humidity_Pasted_Image_10_14_20__12_31_PM.png?1602693147)

![temperature___humidity_3BFCF078-1DC8-4F52-9AEA-E81B5C605BA1_1_105_c.jpeg](https://cdn-learn.adafruit.com/assets/assets/000/095/869/medium640/temperature___humidity_3BFCF078-1DC8-4F52-9AEA-E81B5C605BA1_1_105_c.jpeg?1602790388)

![](https://cdn-learn.adafruit.com/assets/assets/000/095/870/medium800/temperature___humidity_78282014-905E-4BF9-B358-9483A3D722F7_1_105_c.jpeg?1602790400)

# IoT Air Quality Sensor with Adafruit IO

## Code Walkthrough

## Hardware Setup and Configuration
The first chunk of code imports the **settings.toml** file containing WiFi details, Adafruit IO credentials, and location metadata.

```python
# Get WiFi details and Adafruit IO keys, ensure these are setup in settings.toml
# (visit io.adafruit.com if you need to create an account, or if you need your Adafruit IO key.)
ssid = getenv("CIRCUITPY_WIFI_SSID")
password = getenv("CIRCUITPY_WIFI_PASSWORD")
aio_username = getenv("ADAFRUIT_AIO_USERNAME")
aio_key = getenv("ADAFRUIT_AIO_KEY")
```

```python
# AirLift FeatherWing
esp32_cs = DigitalInOut(board.D13)
esp32_reset = DigitalInOut(board.D12)
esp32_ready = DigitalInOut(board.D11)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
status_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.WiFiManager(esp, ssid, password, status_pixel=status_pixel)
```

The PM2.5 sensor is initialized with UART. The BME280 sensor is also initialized with I2C.

```python
# Connect to a PM2.5 sensor over UART
reset_pin = DigitalInOut(board.G0)
reset_pin.direction = Direction.OUTPUT
reset_pin.value = False
uart = busio.UART(board.TX, board.RX, baudrate=9600)
pm25 = PM25_UART(uart, reset_pin)

# Connect to a BME280 sensor over I2C
i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
```

## Reading and Calculating AQI
The `calculate_aqi` function calculates a real-time qualitative Air Quality Index (AQI). This function does not use the quantitative EPA NOWCast Real-Time AQI function since this code does not store values over time. Values are stored and processed by Adafruit IO.

The function takes a sensor reading of PM2.5 size particles and returns both the AQI value and the AQI category.&nbsp;

The Air Quality Index developed by the EPA is divided into **six&nbsp;** categories. Each of the six category names has an index. These categories range from the least amount of health concern ("Good") to immediate health concern where you'll need to don a respirator ("Hazardous").&nbsp;

The breakpoints (_CLow, CHigh, ILow, IHigh_) in this function are provided by the United State's Environmental Protection Agency (EPA), [for more information on the AQI calculation check out this Wikipedia article](https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI).

- If you are in a country other than the United States, this Wikipedia article [contains air quality indices for locations all over the world](https://en.wikipedia.org/wiki/Air_quality_index#Indices_by_location).

```python
### Sensor Functions ###
def calculate_aqi(pm_sensor_reading):
    """Returns a calculated air quality index (AQI)
    and category as a tuple.
    NOTE: The AQI returned by this function should ideally be measured
    using the 24-hour concentration average. Calculating a AQI without
    averaging will result in higher AQI values than expected.
    :param float pm_sensor_reading: Particulate matter sensor value.

    """
    # Check sensor reading using EPA breakpoint (Clow-Chigh)
    if 0.0 <= pm_sensor_reading <= 12.0:
        # AQI calculation using EPA breakpoints (Ilow-IHigh)
        aqi_val = map_range(int(pm_sensor_reading), 0, 12, 0, 50)
        aqi_cat = "Good"
    elif 12.1 <= pm_sensor_reading <= 35.4:
        aqi_val = map_range(int(pm_sensor_reading), 12, 35, 51, 100)
        aqi_cat = "Moderate"
    elif 35.5 <= pm_sensor_reading <= 55.4:
        aqi_val = map_range(int(pm_sensor_reading), 36, 55, 101, 150)
        aqi_cat = "Unhealthy for Sensitive Groups"
    elif 55.5 <= pm_sensor_reading <= 150.4:
        aqi_val = map_range(int(pm_sensor_reading), 56, 150, 151, 200)
        aqi_cat = "Unhealthy"
    elif 150.5 <= pm_sensor_reading <= 250.4:
        aqi_val = map_range(int(pm_sensor_reading), 151, 250, 201, 300)
        aqi_cat = "Very Unhealthy"
    elif 250.5 <= pm_sensor_reading <= 350.4:
        aqi_val = map_range(int(pm_sensor_reading), 251, 350, 301, 400)
        aqi_cat = "Hazardous"
    elif 350.5 <= pm_sensor_reading <= 500.4:
        aqi_val = map_range(int(pm_sensor_reading), 351, 500, 401, 500)
        aqi_cat = "Hazardous"
    else:
        print("Invalid PM2.5 concentration")
        aqi_val = -1
        aqi_cat = None
    return aqi_val, aqi_cat
```

The `sample_aq_sensor` function samples a PM2.5 sensor. Since the PlanTower updates the counts every 2.3 seconds, yet outputs data every 1 second. The sensor could possibly take two successive, identical, samples. This function samples the sensor for 2.3 seconds and averages the amount of samples over the number of samples obtained in that time interval.

- For more information about how the PM2.5 sensor calculates and outputs data, [check out this page of the PM2.5 sensor's learn guide featuring StanJ's analysis report](https://learn.adafruit.com/pm25-air-quality-sensor/usage-notes).

```python
def sample_aq_sensor():
    """Samples PM2.5 sensor
    over a 2.3 second sample rate.

    """
    aq_reading = 0
    aq_samples = []

    # initial timestamp
    time_start = time.monotonic()
    # sample pm2.5 sensor over 2.3 sec sample rate
    while time.monotonic() - time_start <= 2.3:
        try:
            aqdata = pm25.read()
            aq_samples.append(aqdata["pm25 env"])
        except RuntimeError:
            print("Unable to read from sensor, retrying...")
            continue
        # pm sensor output rate of 1s
        time.sleep(1)
    # average sample reading / # samples
    for sample in range(len(aq_samples)):
        aq_reading += aq_samples[sample]
    aq_reading = aq_reading / len(aq_samples)
    aq_samples.clear()
    return aq_reading
```

The `read_bme_280` function reads the BME280 sensor temperature and humidity and returns it as a tuple. If you set `USE_CELSIUS` at the top of the code to `True`, the temperature value will be returned in Celsius instead of Fahrenheit.

```python
def read_bme280(is_celsius=False):
    """Returns temperature and humidity
    from BME280 environmental sensor, as a tuple.

    :param bool is_celsius: Returns temperature in degrees celsius
                            if True, otherwise fahrenheit.
    """
    humid = bme280.humidity
    temp = bme280.temperature
    if not is_celsius:
        temp = temp * 1.8 + 32
    return temperature, humid
```

## Adafruit IO Setup and Configuration
Next is the Adafruit IO initialization and configuration. An instance of the Adafruit IO HTTP client is created.

```python
# Create an instance of the Adafruit IO HTTP client
io = IO_HTTP(aio_username, aio_key, wifi)
```

The feeds you created earlier are initialized using calls to `get_feed`.&nbsp;

```python
# Describes feeds used to hold Adafruit IO data
feed_aqi = io.get_feed("air-quality-sensor.aqi")
feed_aqi_category = io.get_feed("air-quality-sensor.category")
feed_humidity = io.get_feed("air-quality-sensor.humidity")
feed_temperature = io.get_feed("air-quality-sensor.temperature")
```

The location values (latitude, longitude, elevation) are pulled from your settings file and initialized as a tuple, `location_metadata`.

```python
# Set up location metadata from settings.toml file
location_metadata = {
    "lat": getenv("latitude"),
    "lon": getenv("longitude"),
    "ele": getenv("elevation"),
}
```

## Main Loop

The `while True` loop fetches the current time from Adafruit IO's time API (we don't need a real-time-clock or timezone calculations) and reads/publishes sensor data to Adafruit IO when a time interval elapses.&nbsp;

### Obtaining time from Adafruit IO
The Adafruit IO time service does not replace a time-synchronization service like NTP, but it can help you figure out your local time on an Internet of Things device that doesn't have a built in clock.

Instead of using a software-based timer, this code will fetch the current time from Adafruit IO using a call to `receive_time()` every 30 seconds. Then, it will keep track of the minutes elapsed.

```python
try:
        print("Fetching time...")
        cur_time = io.receive_time()
        print("Time fetched OK!")
        # Hourly reset
        if cur_time.tm_min == 0:
            prv_mins = 0
    except (ValueError, RuntimeError) as e:
        print("Failed to fetch time, retrying\n", e)
        wifi.reset()
        wifi.connect()
        continue

    if cur_time.tm_min >= prv_mins:
        print("%d min elapsed.."%elapsed_minutes)
        prv_mins = cur_time.tm_min
        elapsed_minutes += 1
```

### Sample and Publish Data to Adafruit IO

When `PUBLISH_INTERVAL` elapses, the loop will sample the air quality sensor and environmental sensor. Values are printed to the REPL.

Once values are obtained, each value is published to its respective Adafruit IO Feed using calls to `io.send_data`.&nbsp;

Finally, the sensor sleeps 30 seconds before running again.

```python
if elapsed_minutes >= PUBLISH_INTERVAL:
        print("Sampling AQI...")
        aqi_reading = sample_aq_sensor()
        aqi, aqi_category = calculate_aqi(aqi_reading)
        print("AQI: %d"%aqi)
        print("Category: %s"%aqi_category)

        # temp and humidity
        print("Sampling environmental sensor...")
        temperature, humidity = read_bme280(USE_CELSIUS)
        print("Temperature: %0.1f F" % temperature)
        print("Humidity: %0.1f %%" % humidity)

        # Publish all values to Adafruit IO
        print("Publishing to Adafruit IO...")
        try:
            io.send_data(feed_aqi["key"], str(aqi), location_metadata)
            io.send_data(feed_aqi_category["key"], aqi_category)
            io.send_data(feed_temperature["key"], str(temperature))
            io.send_data(feed_humidity["key"], str(humidity))
            print("Published!")
        except (ValueError, RuntimeError) as e:
            print("Failed to send data to IO, retrying\n", e)
            wifi.reset()
            wifi.connect()
            continue
        # Reset timer
        elapsed_minutes = 0
      time.sleep(30)
```


## Guide Products

### Adafruit Feather M4 Express - Featuring ATSAMD51

[Adafruit Feather M4 Express - Featuring ATSAMD51](https://www.adafruit.com/product/3857)
It's what you've been waiting for, the Feather M4 Express featuring ATSAMD51. This Feather is fast like a swift, smart like an owl, strong like a ox-bird (it's half ox, half bird, OK?) This feather is powered by our new favorite chip, the **ATSAMD51J19** -&nbsp; with...

In Stock
[Buy Now](https://www.adafruit.com/product/3857)
[Related Guides to the Product](https://learn.adafruit.com/products/3857/guides)
### Adafruit AirLift FeatherWing – ESP32 WiFi Co-Processor

[Adafruit AirLift FeatherWing – ESP32 WiFi Co-Processor](https://www.adafruit.com/product/4264)
Give your Feather project a _lift_ with the Adafruit AirLift FeatherWing - a FeatherWing that lets you use the powerful ESP32 as a WiFi co-processor. You probably have your favorite Feather ([like the Feather M4](https://www.adafruit.com/product/3857)) that comes with its own...

In Stock
[Buy Now](https://www.adafruit.com/product/4264)
[Related Guides to the Product](https://learn.adafruit.com/products/4264/guides)
### PM2.5 Air Quality Sensor and Breadboard Adapter Kit

[PM2.5 Air Quality Sensor and Breadboard Adapter Kit](https://www.adafruit.com/product/3686)
Breathe easy, knowing that you can track and sense the quality of the air around you with th **e PM2.5&nbsp;Air Quality Sensor with Breadboard Adapter** particulate sensor. [Mad Max & Furiosa definitely should have hooked up one of...](https://en.wikipedia.org/wiki/Mad_Max)

In Stock
[Buy Now](https://www.adafruit.com/product/3686)
[Related Guides to the Product](https://learn.adafruit.com/products/3686/guides)
### Adafruit BME280 I2C or SPI Temperature Humidity Pressure Sensor

[Adafruit BME280 I2C or SPI Temperature Humidity Pressure Sensor](https://www.adafruit.com/product/2652)
Bosch has stepped up their game with their new BME280 sensor, an environmental sensor with temperature, barometric pressure&nbsp;and&nbsp;humidity! This sensor is great for all sorts of indoor environmental sensing and can even be used in both I2C and SPI!

This precision sensor from...

In Stock
[Buy Now](https://www.adafruit.com/product/2652)
[Related Guides to the Product](https://learn.adafruit.com/products/2652/guides)
### FeatherWing Doubler - Prototyping Add-on For All Feather Boards

[FeatherWing Doubler - Prototyping Add-on For All Feather Boards](https://www.adafruit.com/product/2890)
This is the **FeatherWing Doubler** - a prototyping add-on and more for all Feather boards. This is similar to our [FeatherWing Proto](https://www.adafruit.com/products/2884) except there are two! The magic of the Doubler comes when stacking a Feather and another...

In Stock
[Buy Now](https://www.adafruit.com/product/2890)
[Related Guides to the Product](https://learn.adafruit.com/products/2890/guides)
### Flanged Weatherproof Enclosure With PG-7 Cable Glands

[Flanged Weatherproof Enclosure With PG-7 Cable Glands](https://www.adafruit.com/product/3931)
Whether you're raiding tombs or traversing nuclear fallout wastelands, this is the most heavy-duty enclosure for your project! Weatherproof? Check. Tough&nbsp;polycarbonate cover? Check. Built-in gasket seal? Oh yeah, check.

Store your project safe and sound in this **Flanged...**

In Stock
[Buy Now](https://www.adafruit.com/product/3931)
[Related Guides to the Product](https://learn.adafruit.com/products/3931/guides)
### Silicone Cover Stranded-Core Ribbon Cable - 4 Wires 1 Meter Long

[Silicone Cover Stranded-Core Ribbon Cable - 4 Wires 1 Meter Long](https://www.adafruit.com/product/3889)
For those who are fans of our silicone-covered wires, but are always looking to _up their wiring game_. We now have **Silicone Cover Ribbon cables!** These may look _a lot_ like <a...></a...>

In Stock
[Buy Now](https://www.adafruit.com/product/3889)
[Related Guides to the Product](https://learn.adafruit.com/products/3889/guides)
### Lithium Ion Polymer Battery Ideal For Feathers - 3.7V 400mAh

[Lithium Ion Polymer Battery Ideal For Feathers - 3.7V 400mAh](https://www.adafruit.com/product/3898)
Lithium-ion polymer (also known as 'lipo' or 'lipoly') batteries are thin, light, and powerful. The output ranges from 4.2V when completely charged to 3.7V. This battery has a capacity of **400mAh** for a total of about 1.9 Wh. If you need a larger (or smaller!)...

In Stock
[Buy Now](https://www.adafruit.com/product/3898)
[Related Guides to the Product](https://learn.adafruit.com/products/3898/guides)

## Related Guides

- [Adafruit Feather M4 Express](https://learn.adafruit.com/adafruit-feather-m4-express-atsamd51.md)
- [Adafruit AirLift FeatherWing - ESP32 WiFi Co-Processor](https://learn.adafruit.com/adafruit-airlift-featherwing-esp32-wifi-co-processor-featherwing.md)
- [Party Parrot Zoetrope](https://learn.adafruit.com/party-parrot-zoetrope.md)
- [Adafruit Hallowing M0](https://learn.adafruit.com/adafruit-hallowing.md)
- [CircuitScheme - Lisp on CircuitPython](https://learn.adafruit.com/scheme-in-circuitpython.md)
- [CircuitPython displayio Setup for TFT FeatherWings](https://learn.adafruit.com/using-circuitpython-displayio-with-a-tft-featherwing.md)
- [MakeCode Arcade with SAMD51 M4](https://learn.adafruit.com/makecode-arcade-with-samd51-m4.md)
- [Butterfly Bench with Edge Lit Acrylic](https://learn.adafruit.com/butterfly-bench-with-edge-lit-acrylic.md)
- [Networking in CircuitPython](https://learn.adafruit.com/networking-in-circuitpython.md)
- [Super Nintendo USB Controller](https://learn.adafruit.com/super-nintendo-usb-controller.md)
- [3D Printed Dragon Lamp](https://learn.adafruit.com/dragon-lamp.md)
- [Reindeer Mask with Animated Eyes](https://learn.adafruit.com/reindeer-mask-with-animated-eyes.md)
- [Holiday Tree with Feather RP2040 Scorpio](https://learn.adafruit.com/holiday-tree-with-scorpio.md)
- [LED Matrix Scoreboard](https://learn.adafruit.com/led-matrix-scoreboard.md)
- [Rezz-Inspired NeoPixel Glasses](https://learn.adafruit.com/rezz-inspired-neopixel-glasses.md)
- [CircuitPython Libraries on Linux and the 96Boards DragonBoard 410c](https://learn.adafruit.com/circuitpython-libraries-on-linux-and-the-96boards-dragonboard-410c.md)
- [Halo Energy Sword RP2040](https://learn.adafruit.com/halo-energy-sword-rp2040.md)
