
We spend most of our lives indoors — but how often do we actually think about the air we’re breathing? CO₂ buildup, VOCs from furniture, humidity swings, and invisible pollutants all quietly affect focus, sleep, and long-term health. With a Raspberry Pi, a few cheap sensors, and a touch of machine learning, you can build a Smart Air Quality Monitor that tracks your environment 24/7 and pings your phone the moment something’s off.
Perfect for health-conscious makers, home office dwellers, and small businesses who want a healthier workspace.
What You’ll Need
Grab the following:
- Raspberry Pi 4 (2GB or more) — 4GB is comfortable overkill
- MicroSD card (32GB+) with Raspberry Pi OS (64-bit)
- BME280 sensor — temperature, humidity, pressure (I²C)
- MQ-135 sensor — general air quality / VOCs / CO₂ proxy
- SGP30 or CCS811 sensor (optional) — more accurate eCO₂ and TVOC readings
- PMS5003 sensor (optional) — PM2.5 and PM10 particulate matter
- MCP3008 ADC chip — needed to read the analog MQ-135
- Breadboard + jumper wires
- Official Raspberry Pi power supply
- Small case with ventilation holes (airflow matters!)
- Wi-Fi connection
Step 1: Prepare the Raspberry Pi
Flash Raspberry Pi OS (64-bit), boot up, connect to Wi-Fi, and update:
sudo apt update && sudo apt full-upgrade -y
sudo apt install python3-pip python3-smbus i2c-tools -y
pip3 install adafruit-circuitpython-bme280 adafruit-circuitpython-mcp3xxx flask pandas scikit-learn requests
Enable I²C and SPI via sudo raspi-config → Interface Options, then reboot.
Confirm your sensors are detected:
i2cdetect -y 1
Step 2: Wire Up the Sensors
BME280 (I²C):
- VCC → 3.3V
- GND → GND
- SDA → GPIO 2
- SCL → GPIO 3
MQ-135 via MCP3008 (SPI):
- MQ-135 AOUT → MCP3008 CH0
- MCP3008 → 3.3V, GND, CLK (GPIO 11), MISO (GPIO 9), MOSI (GPIO 10), CS (GPIO 8)
PMS5003 (optional): connects via USB-to-serial or directly to the Pi’s UART pins.
Keep sensors away from direct heat sources like the Pi’s CPU — heat skews temperature and humidity readings badly.
Step 3: Write the Sensor Reading Script
Create monitor.py to poll your sensors every minute and log data to a CSV + SQLite database:
import time, sqlite3, board, busio
from adafruit_bme280 import basic as bme280
import digitalio, adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
i2c = busio.I2C(board.SCL, board.SDA)
bme = bme280.Adafruit_BME280_I2C(i2c, address=0x76)
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
cs = digitalio.DigitalInOut(board.D8)
mcp = MCP.MCP3008(spi, cs)
mq135 = AnalogIn(mcp, MCP.P0)
db = sqlite3.connect("air.db")
db.execute("""CREATE TABLE IF NOT EXISTS readings
(ts TEXT, temp REAL, humidity REAL, pressure REAL, voc REAL)""")
while True:
t = bme.temperature
h = bme.humidity
p = bme.pressure
v = mq135.value
db.execute("INSERT INTO readings VALUES (?,?,?,?,?)",
(time.strftime("%Y-%m-%d %H:%M:%S"), t, h, p, v))
db.commit()
print(f"{t:.1f}°C {h:.0f}% {p:.0f}hPa VOC:{v}")
time.sleep(60)
Step 4: Add AI-Powered Anomaly Detection
Instead of dumb fixed thresholds, let a simple ML model learn your normal baseline and alert you when things drift.
Use Isolation Forest from scikit-learn — lightweight and perfect for spotting weird air quality patterns:
from sklearn.ensemble import IsolationForest
import pandas as pd
df = pd.read_sql("SELECT temp, humidity, voc FROM readings", sqlite3.connect("air.db"))
model = IsolationForest(contamination=0.05).fit(df)
def is_anomaly(temp, hum, voc):
return model.predict([[temp, hum, voc]])[0] == -1
Retrain it weekly on your latest data so it adapts to seasons and daily rhythms.
Step 5: Send Smart Alerts to Your Phone
Hook it up to Telegram for instant notifications:
import requests
def alert(msg):
requests.post("https://api.telegram.org/bot<TOKEN>/sendMessage",
data={"chat_id": "<CHAT_ID>", "text": f"⚠️ Air Alert: {msg}"})
Trigger an alert whenever is_anomaly() returns True, or when values cross health-based limits (e.g., humidity <30% or >60%, VOC spikes, CO₂ > 1000 ppm).
Step 6: Build a Live Dashboard
A quick Flask + Chart.js dashboard lets you visualize trends from any device:
from flask import Flask, render_template_string
import sqlite3
app = Flask(__name__)
@app.route("/")
def home():
db = sqlite3.connect("air.db")
rows = db.execute("SELECT ts, temp, humidity, voc FROM readings ORDER BY ts DESC LIMIT 100").fetchall()
return render_template_string("""
<h1>🌿 Air Quality Monitor</h1>
<table border=1 cellpadding=5>
<tr><th>Time</th><th>Temp</th><th>Humidity</th><th>VOC</th></tr>
{% for r in rows %}<tr><td>{{r[0]}}</td><td>{{r[1]}}</td><td>{{r[2]}}</td><td>{{r[3]}}</td></tr>{% endfor %}
</table>
""", rows=rows)
app.run(host="0.0.0.0", port=5000)
Open http://<your-pi-ip>:5000 on any device to see live readings. For fancier graphs, pipe data into Grafana + InfluxDB — a popular combo for IoT dashboards.
Step 7: Auto-Start on Boot
Wrap both scripts in systemd services so the Pi starts monitoring automatically after power cuts. Set it, forget it, breathe easier.
Tips for Accurate Readings
- Let the MQ-135 “burn in” for 24–48 hours before trusting its numbers
- Place the monitor at head height, away from windows and vents
- Avoid direct sunlight — it cooks sensors and skews temperature
- Ventilate the case — stagnant air = useless readings
- Calibrate periodically by comparing against a known reference meter
Wrapping Up
You’ve just built a private, intelligent air quality monitor that doesn’t just log numbers — it actually learns what your home normally feels like and warns you when something changes. Expand it with outdoor sensors, room-by-room units, or automate your air purifier to switch on when VOCs spike.
Because the first step to breathing better… is knowing what you’re breathing. 🌬️💚
Loved this project? Follow @raspitips on Instagram for more Raspberry Pi builds, tips, and tutorials.


