Building a home air quality monitor with PM2.5, CO2, and VOC sensors using ESP32 is especially relevant for Indian cities where indoor air quality is often worse than outdoor. Delhi, Mumbai, Bengaluru, and dozens of Indian cities regularly experience hazardous AQI levels — and indoor pollutants from cooking, incense, cleaning products, and vehicle exhaust infiltration make monitoring crucial. This guide builds a comprehensive indoor air quality monitor with real-time dashboard using ESP32.
Table of Contents
- Why Air Quality Monitoring Matters in India
- Sensor Selection Guide
- Hardware Setup
- ESP32 Air Quality Monitor Code
- Web Dashboard with Real-Time Graphs
- Home Assistant Integration
- Automated Ventilation Alerts
- Frequently Asked Questions
Why Air Quality Monitoring Matters in India
India’s air quality crisis makes indoor monitoring critical:
- PM2.5: Fine particulate matter smaller than 2.5 microns. WHO safe limit: 15 μg/m³. Most Indian cities regularly exceed 100 μg/m³ outdoors. Indoor levels from cooking on gas stoves can reach 200–500 μg/m³ during cooking.
- CO2: Carbon dioxide builds up in closed rooms. Above 1000 ppm reduces cognitive performance. Above 2000 ppm causes headaches. Indian homes and offices with poor ventilation commonly reach 1500–3000 ppm.
- VOC (Volatile Organic Compounds): From paints, cleaning products, furniture off-gassing, and incense. Long-term exposure linked to respiratory issues.
- Diwali impact: Fireworks during Diwali cause extreme PM2.5 spikes (200–500+ μg/m³) that penetrate even well-sealed homes.
Sensor Selection Guide
- PM2.5 sensor:
- PMS5003 (Plantower): Best accuracy, UART interface, ₹1000–₁500. Used in professional AQI monitors.
- PMS7003: Same as PMS5003 but smaller. ₹1200–₁800.
- GP2Y1010AU0F: Analog, ₹400–₆00. Less accurate but more affordable.
- CO2 sensor:
- MH-Z19B: NDIR CO2 sensor, UART/PWM, 0–5000 ppm, ₹800–₁200. Best value for India.
- SCD41: Accurate, I2C, but ₹2000–₃000. For research-grade measurements.
- CCS811: Measures eCO2 (estimated CO2) and VOC via I2C. ₹400–₆00. Less accurate than NDIR but affordable.
- VOC sensor:
- BME688: Bosch sensor with VOC + temperature + humidity + barometric, I2C. ₹600–₁000.
- SGP30: Dedicated VOC + eCO2, I2C. ₹400–₆00.
- MQ-135: Analog gas sensor. ₹50–₁00. Rough VOC indicator, needs calibration.
Hardware Setup
/* Pin connections for ESP32 Air Quality Monitor */
// PMS5003 PM2.5 sensor (UART):
// PMS5003 TX → ESP32 GPIO16 (RX2)
// PMS5003 RX → ESP32 GPIO17 (TX2)
// PMS5003 VCC → 5V
// PMS5003 GND → GND
// PMS5003 SET → 3.3V (always on)
// PMS5003 RESET → 3.3V
// MH-Z19B CO2 sensor (UART):
// MH-Z19B TX → ESP32 GPIO25 (any available RX)
// MH-Z19B RX → ESP32 GPIO26 (any available TX)
// MH-Z19B VCC → 5V (IMPORTANT: needs 5V, NOT 3.3V)
// MH-Z19B GND → GND
// SGP30 VOC sensor (I2C):
// SGP30 SDA → ESP32 GPIO21 (I2C SDA)
// SGP30 SCL → ESP32 GPIO22 (I2C SCL)
// SGP30 VCC → 3.3V
// SGP30 GND → GND
// OLED Display (0.96" SSD1306, I2C):
// Same I2C bus as SGP30 (I2C address 0x3C)
// Note: ESP32 has multiple hardware UARTs - use UART1 for PMS5003, UART2 for MH-Z19B
ESP32 Air Quality Monitor Code
#include <WiFi.h>
#include <PMS.h> // PMS5003 library
#include <MHZ19.h> // MH-Z19B library
#include <Adafruit_SGP30.h> // SGP30 library
#include <Wire.h>
#include <Adafruit_SSD1306.h>
// UART for sensors
HardwareSerial pmsSerial(1); // UART1 for PMS5003
HardwareSerial co2Serial(2); // UART2 for MH-Z19B
PMS pms(pmsSerial);
PMS::DATA pmsData;
MHZ19 mhz19;
Adafruit_SGP30 sgp30;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
struct AirQuality {
uint16_t pm1;
uint16_t pm25;
uint16_t pm10;
int16_t co2;
uint16_t tvoc;
float temperature;
float humidity;
int aqi; // Calculated AQI
};
AirQuality aq;
// Calculate India AQI from PM2.5 (CPCB standard)
int calculateIndiaAQI(float pm25) {
if (pm25 <= 30) return map(pm25, 0, 30, 0, 50);
if (pm25 <= 60) return map(pm25, 31, 60, 51, 100);
if (pm25 <= 90) return map(pm25, 61, 90, 101, 200);
if (pm25 <= 120) return map(pm25, 91, 120, 201, 300);
if (pm25 <= 250) return map(pm25, 121, 250, 301, 400);
return map(pm25, 251, 500, 401, 500);
}
String getAQICategory(int aqi) {
if (aqi <= 50) return "Good";
if (aqi <= 100) return "Satisfactory";
if (aqi <= 200) return "Moderate";
if (aqi <= 300) return "Poor";
if (aqi <= 400) return "Very Poor";
return "Severe";
}
void readAllSensors() {
// Read PMS5003
if (pms.readUntil(pmsData)) {
aq.pm1 = pmsData.PM_AE_UG_1_0;
aq.pm25 = pmsData.PM_AE_UG_2_5;
aq.pm10 = pmsData.PM_AE_UG_10_0;
aq.aqi = calculateIndiaAQI(aq.pm25);
}
// Read MH-Z19B
aq.co2 = mhz19.getCO2();
// Read SGP30
if (sgp30.IAQmeasure()) {
aq.tvoc = sgp30.TVOC;
}
}
void displayOLED() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.printf("PM2.5: %d ug/m3
", aq.pm25);
display.printf("AQI: %d (%s)
", aq.aqi, getAQICategory(aq.aqi).c_str());
display.printf("CO2: %d ppm
", aq.co2);
display.printf("TVOC: %d ppb
", aq.tvoc);
display.display();
}
void setup() {
Serial.begin(115200);
Wire.begin();
pmsSerial.begin(9600, SERIAL_8N1, 16, 17);
co2Serial.begin(9600, SERIAL_8N1, 25, 26);
mhz19.begin(co2Serial);
mhz19.autoCalibration(false); // Disable for indoor use
sgp30.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
WiFi.begin("YourSSID", "YourPass");
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void loop() {
readAllSensors();
displayOLED();
publishMQTT();
delay(30000); // Read every 30 seconds
}
Home Assistant Integration
# ESPHome configuration for HA integration (alternative approach):
sensor:
- platform: pmsx003
type: PMS5003
uart_id: pms_uart
pm_1_0:
name: "PM1.0"
pm_2_5:
name: "PM2.5"
id: pm25
pm_10_0:
name: "PM10"
- platform: mhz19
co2:
name: "CO2"
temperature:
name: "MHZ19 Temperature"
uart_id: co2_uart
update_interval: 60s
automatic_calibration: false
- platform: sgp30
eco2:
name: "eCO2"
tvoc:
name: "TVOC"
# Alert automation in HA:
automation:
- alias: "Poor Air Quality Alert"
trigger:
- platform: numeric_state
entity_id: sensor.pm2_5
above: 60
action:
- service: notify.telegram
data:
message: "Indoor PM2.5: {{ states('sensor.pm2_5') }} μg/m³ — Open windows!"
Automated Ventilation Alerts
// Smart ventilation control based on air quality:
// When PM2.5 > 60 AND outdoor AQI > indoor: keep windows closed, run air purifier
// When PM2.5 > 60 AND outdoor AQI 300) {
Serial.println("HAZARDOUS: Wear N95 mask indoors!");
sendTelegramAlert("🚨 Indoor AQI " + String(aq.aqi) + " - Very Poor! Close all windows.");
} else if (aq.co2 > 1500) {
Serial.println("CO2 HIGH: Ventilate the room");
sendTelegramAlert("⚠️ CO2: " + String(aq.co2) + " ppm - Open windows for fresh air");
}
}
Frequently Asked Questions
How accurate are PMS5003 sensors for Indian PM2.5 measurement?
PMS5003 uses laser particle counting and correlates well with professional equipment (within 20% at typical concentrations). In high humidity (Indian monsoon), laser sensors can overcount due to water droplets. Under 85% humidity the readings are reliable. For precise measurements, BME280 humidity data can be used to apply humidity correction factors.
Does MH-Z19B need calibration for Indian conditions?
Disable automatic calibration (ABC – Auto Baseline Correction) for indoor monitors because ABC assumes the sensor is exposed to outdoor air (400 ppm CO2 baseline) at least once in 24 hours. Indian indoor monitors never reach outdoor air baseline. Use only when manually calibrating outdoors for 20+ minutes.
What is a safe PM2.5 level for Indian indoor air?
WHO guideline: 15 μg/m³ annual mean. India’s NAAQS: 40 μg/m³. Practically, under 35 μg/m³ is considered acceptable indoor; under 12 μg/m³ is excellent. During Diwali season or winter, prioritise air purifiers and monitor continuously.
Which air purifiers work with this ESP32 monitor?
Xiaomi Mi Air Purifier and Philips Series 1000i/2000i support Home Assistant via Xiaomi integration. Connect the air purifier to HA and create automations based on PM2.5 sensor readings. The ESP32 monitor publishes PM2.5 data → HA receives it → automatically controls the purifier.
Can I monitor air quality in multiple rooms with one ESP32?
Each room needs its own ESP32 + sensor node for distributed monitoring. Use MQTT — each sensor node publishes to home/airquality/room_name/pm25 etc. A central Raspberry Pi running Home Assistant collects all readings and shows them in one dashboard. Total cost: ₹2000–₃000 per room node.
Add comment