How to Track Indoor Air Pollution with a Raspberry Pi

You ever wonder what's actually in the air you breathe every day? I've lived in Beijing for a few years now and I'm always running a couple of air purifiers in my home, but how well do they actually work? Sure some have built in sensors, but how accurate are they? Well with a little Python, a Raspberry Pi, and some moxie, we can find our own answers.

This will focus primarily on measuring PM2.5 (opens new window) and PM10 (opens new window) in the air and converting the values to an Air Quality Index (AQI (opens new window)). For measuring other harmful chemicals like Nitrogen Dioxide (NO2) or Carbon Monoxide (CO), see the "Adding Sensors" section at the end of the article

# Prerequisites

# Configure the Raspberry Pi

Now we can get down to business. Power up your Raspberry Pi, attach your sensor, and crack your knuckles emphatically.

# Setting up Adafruit IO

Create an account on Adafruit IO (opens new window). This is a great site to collect data streams and display them on custom dashboards. After you've created an account, let's create a couple of feeds. We'll need three: a pm2.5, a pm10, and a log feed (I named mine "beijing-twofive", "beijing-ten", and "logs" respectively). After that, you can either create a dashboard now or later and play around with how you want to display the data. This is how I have mine set up.

Adafruit IO Dashboard

# Configure your Raspberry Pi

Configure (opens new window) your Raspberry Pi with the installers on the product website. Once configured with either Raspbian or a Linux distribution of your choice (this project should be compatible with most distributions, but it's only been tested on Debian, Raspbian, and MacOS), install the following dependencies:

# Download the Code and Install Dependencies

SSH into your Raspberry Pi (or login if GUI is installed and open your terminal) and download the repo in your home directory:

git clone [email protected]:HoukasaurusRex/RaspberryPi-AQIPi.git

Then install the python dependencies:

pip3 install -r requirements.txt

# Configure ENV with Adafruit Keys and Feed Names

Retrieve your AdafruitIO username and key from the dashboard and add them to you env.

echo 'AIO_USERNAME="Hackerman"
AIO_LOGS="logs"' > .env

Both CITY and AIO_LOGS are feed names created in the AdafruitIO dashboard.

# Run the Code

Now you can run the code. I like to use screen (opens new window) to save my terminal process to be accessible later, but you can just run it in your main shell as well.

screen -S aqipi
cd ~/RaspberryPi-AQIPi
chmod +x

There won't be any output in your terminal, but you should be able to go to your AdafruitIO feeds and start seeing results!

# Sensor Placement

Standard advice for locating your sensor is that it should be outside and four metres above ground level. That’s good advice for general environmental monitoring; however, we’re not necessarily interested in general environmental monitoring – we’re interested in knowing what we’re breathing in.

Choose a location where you spend most of your time or where you might be particularly interested in the general air quality (e.g. in the kitchen or garage) and place your sensor in a safe place where it won't be affected by excessive moisture or humidity.

# Understanding the Code

def read_data():
  pm_twofive_data = []
  pm_ten_data = []
  readings = 0

  # This will take 11 data samples and use the built in `statistics` module to upload a median value
  # to filter out excessive data spikes in the readings
  while readings < 11:
    data = []
    for index in range(0, 10):
      datum =

    # Convert the readings from bytes to ints and append to the array of data samples
    pm_twofive = int.from_bytes(b''.join(data[2:4]), byteorder='little') / 10
    pm_ten = int.from_bytes(b''.join(data[4:6]), byteorder='little') / 10
    readings += 1

    # Take a little break ☕️
  # Calculate the AQI from ppm^2 using the US EPA API table detailed in the section below
  # then upload to your AdafruitIO feeds using the Adafruit_IO SDK
  pm_twofive_aqi = calc_aqi('pm_twofive', median(pm_twofive_data))
  send_data('twofive', pm_twofive_aqi)
  pm_ten_aqi = calc_aqi('pm_ten', median(pm_ten_data))
  send_data('ten', pm_ten_aqi)
  return [pm_twofive_aqi, pm_ten_aqi]

# Converting PPM^2 to AQI

This is converting to the US EPA AQI (opens new window), in order to use a different standard, you might need to tweak the formula to fit your country's AQI model.

US EPA AQI Formula: I = (I_high - I_low) / (C_high - C_low) * (C - C_low) + I_low

# Exponential Backoff

This repo implements an exponential backoff policy to retry connections after an exponentially longer period to account for common network errors or connection issues with your sensor.

def exponential_backoff(n):
  return (2 ** n) + (random.randint(0, 1000) / 1000)

# Results

First testing against the air quality outside, make sure it seems to match up with IQ Air's (opens new window) Air Quality Index.

After a few weeks of running the sensor, it seems the sensors on my air purifiers would often underreport pm2.5 values by as much as 50% and were seldom correlated with each other. The Raspberry Pi on the other hand, filtering out for data spikes, seems to respond very reasonably to real world phenomena such as rising with the outdoor AQI and falling linearly when the purifiers are all left on high for a few hours. I have also learned how much barometric pressure differences will also increase the penetration of outdoor pollution to my home. Interestingly, poor kitchen ventilation also causes quite noticeable spikes in indoor air pollution!

I've been using this sensor a few months now and have since felt much more empowered with managing the quality of the air I breathe each day.

# Adding Sensors

This project can be easily extended by adding additional sensors, such as an Ozone (O3), Carbon Monoxide (CO), Nitrogen Dioxide (NO2), or any other harmful air pollutants that might be more relevant to your area. If you do, please let me know and I'd like to compare your findings and update the AQIPi repository to extend the project.

# Acknowledgements

Big thanks to Andrew Gregory from (opens new window) on his work providing the inspiration for this project.