Skip to content
IntermediateESP8266 / NodeMCUESP8266AQIAQI SYSTEM

🌿 AirWatch — Air Quality Monitoring System

# 🌿 AirWatch — Air Quality Monitoring System > **ESP8266 + MQ135 + SSD1306 OLED + Active Buzzer + Web Dashboard** A self-contained air quality monitor that reads CO₂-equivalent gas concentration, displays live AQI on an OLED, fires a buzzer alert at AQI ≥ 110, and streams real-time data to a green-themed web dashboard over Wi-Fi.

Ksp_wirespace7/5/2026 11 min read 0 views
Source code
🌿 AirWatch — Air Quality Monitoring System
Image

🌿 AirWatch — Air Quality Monitoring System

ESP8266 + MQ135 + SSD1306 OLED + Active Buzzer + Web Dashboard

A self-contained air quality monitor that reads CO₂-equivalent gas concentration, displays live AQI on an OLED, fires a buzzer alert at AQI ≥ 110, and streams real-time data to a green-themed web dashboard over Wi-Fi.


📋 Table of Contents

  1. Overview
  2. Hardware Requirements
  3. Wiring
  4. Software Setup
  5. Calibration
  6. Normal Operation
  7. Web API
  8. Web Dashboard
  9. Firmware Reference
  10. Troubleshooting
  11. File Structure

1. Overview

Project previewProject preview

AirWatch reads air quality using an MQ135 sensor every 2 seconds, converts the raw ADC value into a CO₂ PPM estimate, then maps that to a standard AQI index. The result is shown on a 128×64 OLED display and served as JSON over Wi-Fi to a browser dashboard.

When AQI crosses 110 (lower boundary of the Sensitive Groups band), an active buzzer fires a single 300 ms beep. The buzzer re-arms automatically once the AQI drops back below 110, so it will beep again if the threshold is crossed a second time.

Key Features

  • Real-time MQ135 sensor reading every 2 seconds
  • CO₂ PPM calculation using the MQ135 power-law curve
  • PPM → AQI conversion across six standard EPA bands
  • Live OLED display: AQI, PPM, category, and alert state
  • Single-beep buzzer alert at AQI ≥ 110 (auto re-arms on recovery)
  • Wi-Fi HTTP server serving JSON at GET /data
  • Green-themed animated web dashboard with ring gauge, history chart, and stats
  • Built-in calibration mode for accurate RZERO determination

2. Hardware Requirements

ComponentDetails
MicrocontrollerESP8266 NodeMCU v1/v2 (or any ESP8266 module)
Gas SensorMQ135 Air Quality Sensor module
DisplaySSD1306 0.96″ OLED — 128×64 px — I²C address 0x3C
BuzzerActive buzzer (3.3 V or 5 V with transistor)
ResistorsTwo 10 kΩ resistors (voltage divider for MQ135 AOUT → A0)
Power5 V micro-USB to NodeMCU + 5 V to MQ135 VCC
MiscJumper wires, breadboard or PCB

3. Wiring

Pin Map

ComponentPinESP8266 PinNotes
MQ135AOUTA0Via 10 kΩ + 10 kΩ voltage divider
MQ135VCCVin (5 V)MQ135 heater requires 5 V
MQ135GNDGND
OLEDSDAD2 (GPIO4)I²C data
OLEDSCLD1 (GPIO5)I²C clock
OLEDVCC3.3 V
OLEDGNDGND
Buzzer+D5 (GPIO14)Active buzzer: HIGH = ON
BuzzerGND

⚠️ Voltage Divider — Critical for ESP8266

The ESP8266 ADC (A0) accepts a maximum of 1.0 V. The MQ135 AOUT pin can output up to 5 V.

MQ135 AOUT ──┬── 10 kΩ ── A0
│
10 kΩ
│
GND

Connect two 10 kΩ resistors in series between AOUT and GND. Tap A0 from the midpoint. This is already reflected in the firmware as #define VCC 1.0.


4. Software Setup

4.1 Arduino IDE & Board

  1. Open Arduino IDE → File → Preferences → Additional Board URLs
  2. Add: https://arduino.esp8266.com/stable/package_esp8266com_index.json
  3. Tools → Board Manager → search esp8266 → Install
  4. Select: Tools → Board → NodeMCU 1.0 (ESP-12E Module)
  5. Set Upload Speed: 115200, Port: your COM/ttyUSB port

4.2 Required Libraries

Install via Sketch → Include Library → Manage Libraries:

LibraryVersion
Adafruit SSD1306Latest
Adafruit GFX LibraryLatest
ArduinoJson6.x (not v5 or v7)
ESP8266WiFiBuilt-in with board package
ESP8266WebServerBuilt-in with board package

4.3 Configuration

Edit the top of air_quality_esp8266.ino before uploading:

// Wi-Fi credentials
const char* WIFI_SSID     = "YourNetworkName";
const char* WIFI_PASSWORD = "YourPassword";

// Calibration
#define CALIBRATION_MODE  false   // true = calibration, false = normal run
#define MQ135_RZERO       30.0   // paste your calibrated value here
#define MQ135_RLOAD       10.0   // load resistor on MQ135 board (kΩ)
#define VCC               1.0    // ESP8266 ADC reference (1 V after voltage divider)

5. Calibration

The MQ135_RZERO constant is unique to each sensor and must be measured in clean air. Skipping this step will give inflated PPM and AQI readings.

Step-by-step

  1. Set #define CALIBRATION_MODE true in the firmware
  2. Upload to the ESP8266
  3. Take the sensor outdoors or near an open window
  4. Open Serial Monitor at 115200 baud
  5. Wait at least 5 minutes for the heater to stabilise — watch the RZERO column
  6. Note the value once it stabilises (typically 20–50 with voltage divider)
  7. Paste it into #define MQ135_RZERO <your value>, set CALIBRATION_MODE false, re-upload

Expected Serial Output (Calibration Mode)

RAW: 236 | Rs: 6.85 kOhm | RZERO: 28.43  <-- use this after 5 min
RAW: 237 | Rs: 6.81 kOhm | RZERO: 28.27  <-- use this after 5 min
RAW: 235 | Rs: 6.89 kOhm | RZERO: 28.61  <-- use this after 5 min

Tip: Take the average of readings over the last 2 minutes of the warm-up period.


6. Normal Operation

OLED Display Layout

+-----------------------------+
| AIR QUALITY MONITOR         |
|_____________________________|
|  87           AQI           |
|               580 ppm       |
|_____________________________|
| Moderate       RAW: 236     |
+-----------------------------+

When AQI ≥ 110, the bottom line shows !! Sensitive !! instead of the plain category.

Serial Monitor Output

RAW: 236 | PPM: 580.0 | AQI: 87.0  | Moderate
RAW: 241 | PPM: 620.3 | AQI: 95.2  | Moderate
RAW: 255 | PPM: 714.8 | AQI: 104.9 | Sensitive

AQI Bands

AQI RangeCategoryDescription
0 – 50GoodAir quality is satisfactory
51 – 100ModerateAcceptable; some pollutants may be a concern for a small number of sensitive people
101 – 150SensitiveMembers of sensitive groups may experience health effects
151 – 200UnhealthyEveryone may begin to experience health effects
201 – 300Very UnhealthyHealth alert; everyone may experience more serious effects
301+HazardousHealth warnings of emergency conditions

Buzzer Behaviour

  • Fires one 300 ms beep the moment AQI first reaches or exceeds 110
  • Re-arms once AQI drops below 110 — will beep again if threshold is crossed again
  • Does not beep continuously while AQI stays above 110

7. Web API

The ESP8266 runs an HTTP server on port 80. Its IP address is printed to both the OLED and Serial Monitor at boot.

Endpoints

MethodEndpointResponse
GET/Plain-text status and IP
GET/dataJSON with current readings

Sample JSON Response

{
  "aqi":      87,
  "ppm":      580,
  "category": "Moderate",
  "alert":    false,
  "ip":       "192.168.1.42"
}

The response includes Access-Control-Allow-Origin: * so any browser tab or local HTML file can fetch it without CORS errors.


8. Web Dashboard

webdashboardwebdashboard

Open airwatch_dashboard.html in any modern browser. It runs a demo animation automatically before connecting to hardware.

Connecting to Live Hardware

  1. Find the ESP8266 IP address on the OLED at boot or in Serial Monitor
  2. Enter the IP into the ESP IP field in the dashboard
  3. Click Connect — data refreshes every 2.5 seconds

Dashboard Components

ComponentDescription
Ring GaugeAnimated circular gauge with tick marks; colour changes per AQI band
AQI NumberLarge number with matching colour glow
PPM ReadingShown inside the gauge below the AQI value
Category BadgeColour-coded pill showing the current band name
Alert BannerRed flashing banner when AQI ≥ 110
AQI ScaleSix colour-coded bands; active band highlights live
History ChartSparkline of last 40 readings with dashed alert line at 110
Stats RowCurrent / Min / Max / Avg / Reading count / Alert count

9. Firmware Reference

Key Functions

FunctionDescription
rawToPPM(int raw)Converts ADC value to CO₂-equivalent PPM using MQ135 power-law curve
ppmToAQI(float ppm)Maps PPM to AQI across six linear segments
aqiCategory(float aqi)Returns string category name for an AQI value
updateDisplay(int raw)Redraws OLED with AQI, PPM, category, and alert state
singleBeep()Drives BUZZER_PIN HIGH for 300 ms then LOW
handleData()HTTP handler — serialises state to JSON with CORS header
mapf(x, in_min, ...)Float-precision version of Arduino map()

Timing

EventInterval
Sensor read + OLED updateEvery 2000 ms
Buzzer pulse duration300 ms
Dashboard fetch intervalEvery 2500 ms (client-side)

PPM Formula

Rs    = ((VCC - Vout) / Vout) × RLOAD
ratio = Rs / RZERO
PPM   = 116.6020682 × ratio^(−2.769034857)

10. Troubleshooting

SymptomFix
AQI shows 301 constantlyRZERO needs calibration. Run calibration mode outdoors for 5+ min and update MQ135_RZERO.
AQI too high right after power-onNormal — MQ135 heater needs 2–5 min warm-up. Wait before trusting readings.
OLED shows nothingCheck SDA→D2 and SCL→D1. Verify I²C address is 0x3C. Some clones use 0x3D.
Wi-Fi never connectsCheck WIFI_SSID / WIFI_PASSWORD. ESP8266 only supports 2.4 GHz networks.
Dashboard shows OFFLINEConfirm the IP is cor
C++

Get new projects straight to your inbox

Arduino, ESP32 & IoT tutorials — new builds, firmware tips, Wokwi guides. No spam.

Q&A is disabled for this project.