Friday, 9 October 2026

AI Real-Time Air Quality Monitoring & Forecasting System

AI Real-Time Air Quality Monitoring & Forecasting System using ESP32, IoT, n8n and Agentic AI

Complete project documentation • Hardware design • Circuit schematic • ESP32 firmware • AI forecasting • n8n workflows • Telegram voice alerts • Google Sheets • ThingSpeak dashboard

This project is an end-to-end smart environmental monitoring system that measures air pollution in real time, sends sensor data to the cloud, predicts possible air-quality deterioration, and automatically alerts users through Telegram—including voice notifications—using an ESP32, n8n automation, and an AI agent.

Air Quality Monitoring System (AQMS) - Hackster.io
Sniffer Air Quality (AQI) Monitor using ESP32 + PMSA003 + BME680 - Bootloader Blog
IoT-Based Environmental Monitoring System | project

1. Project overview

The system combines five major functions:

  • Sensing: Collect air-quality, temperature, and humidity measurements from connected sensors.

  • IoT monitoring: Transmit readings from the ESP32 to ThingSpeak and Google Sheets.

  • AI forecasting: Analyze recent measurements and estimate whether air quality may deteriorate.

  • Agentic automation: Use n8n to evaluate conditions, coordinate AI analysis, and trigger appropriate actions.

  • Intelligent alerts: Send Telegram text and voice alerts when dangerous or abnormal conditions are detected.

The intended result is a live air-quality monitoring webpage and a connected automation system that works with minimal manual intervention.

Project capabilities

Real-time monitoring

Sensor readings and historical trends

AI forecasting

Predicted trends and risk assessment

n8n automation

Rules, decisions and scheduled workflows

Telegram voice alerts

Automated audio notifications and text

2. System architecture

The overall data flow is:

Air and environmental conditions

Particulate matter, gases, temperature, humidity

Sensor layer

PM2.5/PM10 sensor + optional gas sensor + temperature/humidity sensor

ESP32 microcontroller

Read, validate, timestamp and transmit measurements

Wi-Fi and HTTP / HTTPS

Secure data transmission to cloud services

n8n automation engine

Receive data, evaluate rules, call AI, log results and dispatch alerts

ThingSpeak

Live charts and trends

Google Sheets

Historical records

AI forecasting

Future risk estimate

Telegram

Text and audio alerts

The live webpage can read a backend API or ThingSpeak data; it need not connect directly to the ESP32.

End-to-end operating sequence

  1. Sensors sample the air.

  2. The ESP32 reads the measurements and checks that the values are valid.

  3. The ESP32 transmits the data to the cloud at a configured interval.

  4. n8n receives the data and checks for threshold violations, missing readings, and sensor faults.

  5. An AI model evaluates trends and forecasts future air-quality risk.

  6. n8n stores measurements and analysis results in Google Sheets and, where configured, ThingSpeak.

  7. If an alert condition is met, n8n sends a Telegram message and optionally generates and delivers a voice message.

  8. The dashboard displays current conditions, historical measurements, and forecast results.

The system should use deterministic safety rules for urgent alerts. The AI agent provides additional interpretation and forecasting rather than being the sole authority on whether a critical alert is sent.

3. Hardware components required

Use a particulate-matter sensor as the primary air-pollution measurement device. A gas sensor alone is not sufficient to measure PM2.5 or PM10.

GitHub - TronixLab/DOIT_ESP32_DevKit-v1_30P · GitHub

1. ESP32 DevKit V1

Required

The controller reads sensors, connects to Wi-Fi, and uploads readings to cloud services.

Czujnik pyłu/czystości powietrza PM1.0 / PM2.5 / PM10 - PMS5003 - 5V UART Sklep Botland

2. PMS5003 particulate-matter sensor

Recommended

Measures PM1.0, PM2.5 and PM10 estimates. Communicates through UART serial.

Buy DHT 22 Module Online In India. Hyderabad

3. DHT22 temperature/humidity sensor

Provides environmental context for the air-quality readings and forecasting model.

MQ-135 Air Quality Gas Sensor Module | eBay

4. MQ-135 gas sensor (optional)

Provides a gas-sensitive analog signal. Requires warm-up and calibration; do not interpret raw ADC values as accurate ppm measurements.

KEYESTUDIO Starter Kit with 3.3V 5V Breadboard Power Module, 830 points Breadboard kit, 65pcs Jumper Wire for Arduino MEGA2560 R3 - BigaMart

5. Supporting components

Breadboard or PCB, jumper wires, suitable regulated power supply, and level shifting or voltage-divider components where needed.

Estimated project budget

These are indicative planning estimates in Indian rupees, not live quotations.

Component

Estimated cost

ESP32 development board

₹400–900

PMS5003 particulate sensor

₹1,200–2,500

DHT22 sensor

₹150–350

Optional MQ-135 module

₹100–250

Wiring, PCB and power components

₹300–800

Estimated total

₹2,150–4,800

Cloud hosting, an AI API, and voice-generation services may add costs depending on usage. A free or self-hosted n8n setup may reduce some expenses.

4. Circuit schematic and wiring

The following schematic uses the ESP32, PMS5003 and DHT22. The MQ-135 is optional.

【ESP32】PMS5003で空気中の粒子状物質を測定してみた #IoT - Qiita

Pin connection table

Example pin assignment for a classic ESP32 DevKit V1. Confirm the pinout and voltage requirements of your exact sensor modules before powering them.

Sensor pin

ESP32 connection

Purpose

PMS5003 VCC

Suitable regulated supply, typically 5 V

Sensor power

PMS5003 GND

GND

Common ground

PMS5003 TX

GPIO 16 (RX2)

ESP32 receives sensor data

PMS5003 RX

GPIO 17 (TX2), optional

ESP32 sends sensor commands

DHT22 VCC

3.3 V for a compatible module

Sensor power

DHT22 GND

GND

Common ground

DHT22 DATA

GPIO 4

Digital readings

MQ-135 AO, optional

GPIO 34 through a suitable voltage divider

Analog reading

Important electrical precautions

  • Check the PMS5003 model's supply requirements and ensure its UART signal voltage is safe for the ESP32.

  • The ESP32 GPIO inputs are not 5 V tolerant. Never connect a 5 V analog output directly to GPIO 34.

  • A bare DHT22 generally needs a pull-up resistor on its data line; many breakout modules already include one.

  • MQ-135 modules can have analog outputs approaching their supply voltage. Use a properly designed divider and check the maximum voltage under all conditions.

  • Use a stable supply and common ground. Do not power a sensor heater or other high-current load from an ESP32 GPIO.

5. Software and cloud services

Install or create these components before programming the device.

Software/service

Purpose

Arduino IDE

ESP32 firmware development

ESP32 Arduino core

Board support and Wi-Fi

Adafruit DHT sensor library

Temperature/humidity readings

ThingSpeak

Time-series charts and data storage

n8n

Workflow automation and AI orchestration

Google Sheets

Historical log and reports

Telegram Bot API

Text and audio notifications

AI model/API

Forecast explanation and risk assessment

Web dashboard

Live readings, trends, alerts and predictions

References: ESP32 Wi-Fi documentation , ThingSpeak write API , n8n documentation , n8n Telegram integration , and n8n Google Sheets integration .

— Arduino ESP32 latest documentation
+4

 

6. ESP32 firmware — complete starter implementation

This firmware is designed to:

  • Connect to Wi-Fi.

  • Read PM1.0, PM2.5 and PM10 from the PMS5003.

  • Read temperature and humidity from the DHT22.

  • Upload data to ThingSpeak.

  • Send a JSON payload to an n8n webhook.

  • Retry network connections and report basic errors through the serial monitor.

Step 1: Configure the Arduino IDE

  1. Install the ESP32 board package using Boards Manager.

  2. Select the correct ESP32 board and serial port.

  3. Install the DHT sensor library by Adafruit and its Adafruit Unified Sensor dependency.

  4. Connect the PMS5003 TX to ESP32 GPIO 16.

  5. Set the serial monitor to 115200 baud.

Step 2: Create a ThingSpeak channel

Create a channel with the following numeric fields:

  • Field 1: PM1.0 in µg/m³

  • Field 2: PM2.5 in µg/m³

  • Field 3: PM10 in µg/m³

  • Field 4: Temperature in °C

  • Field 5: Relative humidity in %

  • Field 6: Estimated AQI category code, if you implement a defined AQI calculation

Copy the channel's Write API Key. Keep it private.

Step 3: Create an n8n webhook

In n8n, create a workflow with a Webhook node configured as follows:

  • HTTP method: POST

  • Path: air-quality

  • Response mode: respond immediately or use a Respond to Webhook node

  • Expected body: JSON

Activate the workflow and copy its production webhook URL. For a deployed system, use an HTTPS URL.

Step 4: Upload this firmware

Replace the placeholder Wi-Fi credentials, ThingSpeak key and webhook URL before uploading.

cpp

#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <DHT.h>

// ---------- USER CONFIGURATION ----------
const char* WIFI_SSID = "YOUR_WIFI_NAME";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char* THINGSPEAK_WRITE_KEY = "YOUR_WRITE_API_KEY";

// Use your real HTTPS n8n production webhook URL.
const char* N8N_WEBHOOK_URL =
  "https://YOUR_N8N_HOST/webhook/air-quality";

// ---------- HARDWARE ----------
#define PMS_RX 16
#define PMS_TX 17
#define DHT_PIN 4
#define DHT_TYPE DHT22

HardwareSerial pmsSerial(2);
DHT dht(DHT_PIN, DHT_TYPE);

// ---------- DATA STRUCTURE ----------
struct PMData {
  uint16_t pm1;
  uint16_t pm25;
  uint16_t pm10;
};

unsigned long lastSample = 0;
const unsigned long SAMPLE_INTERVAL = 20000;

// Read one valid 32-byte PMS5003 frame.
// The atmospheric/environmental PM values are used.
bool readPMS(PMData &out) {
  static uint8_t frame[32];
  static size_t pos = 0;

  while (pmsSerial.available()) {
    uint8_t b = pmsSerial.read();

    if (pos == 0 && b != 0x42) continue;

    if (pos == 1 && b != 0x4D) {
      pos = (b == 0x42) ? 1 : 0;
      continue;
    }

    frame[pos++] = b;

    if (pos == 32) {
      pos = 0;

      uint16_t length =
        (uint16_t(frame[2]) << 8) | frame[3];

      uint16_t sum = 0;
      for (int i = 0; i < 30; i++) {
        sum += frame[i];
      }

      uint16_t received =
        (uint16_t(frame[30]) << 8) | frame[31];

      if (length != 28 || sum != received) {
        return false;
      }

      out.pm1 =
        (uint16_t(frame[10]) << 8) | frame[11];
      out.pm25 =
        (uint16_t(frame[12]) << 8) | frame[13];
      out.pm10 =
        (uint16_t(frame[14]) << 8) | frame[15];

      return true;
    }
  }

  return false;
}

bool ensureWiFi() {
  if (WiFi.status() == WL_CONNECTED) return true;

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED &&
         millis() - start < 15000) {
    delay(250);
  }

  return WiFi.status() == WL_CONNECTED;
}

// Prototype helper: HTTPS with certificate verification disabled.
// Replace with CA certificate validation for deployment.
int httpsPostJson(const char* url, const String& body) {
  WiFiClientSecure client;
  client.setInsecure();  // Prototype only; not production-secure

  HTTPClient http;
  if (!http.begin(client, url)) return -1;

  http.setTimeout(10000);
  http.addHeader("Content-Type", "application/json");

  int code = http.POST(body);
  http.end();
  return code;
}

bool uploadThingSpeak(
  const PMData &pm, float temp, float humidity
) {
  if (!ensureWiFi()) return false;

  String url = "https://api.thingspeak.com/update?api_key=";
  url += THINGSPEAK_WRITE_KEY;
  url += "&field1=" + String(pm.pm1);
  url += "&field2=" + String(pm.pm25);
  url += "&field3=" + String(pm.pm10);
  url += "&field4=" + String(temp, 1);
  url += "&field5=" + String(humidity, 1);

  WiFiClientSecure client;
  client.setInsecure(); // Prototype only

  HTTPClient http;
  if (!http.begin(client, url)) return false;

  http.setTimeout(10000);
  int code = http.GET();
  String response = http.getString();
  http.end();

  // ThingSpeak normally returns a nonzero entry ID.
  response.trim();
  return code == HTTP_CODE_OK &&
         response.length() > 0 &&
         response != "0";
}

void sendToN8N(
  const PMData &pm, float temp, float humidity
) {
  String json = "{";
  json += "\"device_id\":\"esp32-air-01\",";
  json += "\"pm1\":" + String(pm.pm1) + ",";
  json += "\"pm25\":" + String(pm.pm25) + ",";
  json += "\"pm10\":" + String(pm.pm10) + ",";
  json += "\"temperature_c\":" + String(temp, 1) + ",";
  json += "\"humidity_pct\":" + String(humidity, 1);
  json += "}";

  int code = httpsPostJson(N8N_WEBHOOK_URL, json);

  Serial.print("n8n HTTP status: ");
  Serial.println(code);
}

void setup() {
  Serial.begin(115200);

  pmsSerial.begin(
    9600, SERIAL_8N1, PMS_RX, PMS_TX
  );

  dht.begin();

  WiFi.mode(WIFI_STA);
  ensureWiFi();

  Serial.println("Air quality monitor started.");
}

void loop() {
  if (millis() - lastSample < SAMPLE_INTERVAL) {
    delay(50);
    return;
  }

  lastSample = millis();

  PMData pm;
  if (!readPMS(pm)) {
    Serial.println(
      "No valid PMS5003 frame. Check wiring and sensor."
    );
    return;
  }

  float temp = dht.readTemperature();
  float humidity = dht.readHumidity();

  if (isnan(temp) || isnan(humidity)) {
    Serial.println("DHT22 read failed; skipping upload.");
    return;
  }

  Serial.printf(
    "PM1=%u, PM2.5=%u, PM10=%u, "
    "Temp=%.1f C, RH=%.1f%%\n",
    pm.pm1, pm.pm25, pm.pm10, temp, humidity
  );

  if (!ensureWiFi()) {
    Serial.println("Wi-Fi unavailable.");
    return;
  }

  bool tsOK = uploadThingSpeak(pm, temp, humidity);
  Serial.println(tsOK ?
    "ThingSpeak upload OK" :
    "ThingSpeak upload failed");

  sendToN8N(pm, temp, humidity);
}

Firmware notes

  • The firmware sends data every 20 seconds. Check your ThingSpeak account's write-rate restrictions and increase the interval if necessary.

  • The PMS5003 must have time to initialize and sample the air. In a production version, maintain a rolling sample buffer and use timeouts rather than depending on a fresh complete sensor frame arriving at the exact polling instant.

  • The setInsecure() calls disable TLS certificate verification. They are shown only to simplify a bench prototype. Use certificate validation before deployment.

  • The example intentionally does not publish a fabricated AQI number. Add a jurisdiction-specific AQI calculation before showing an AQI value.

  • If Wi-Fi fails, this basic implementation does not maintain a persistent offline queue. That can be added using flash storage or an SD card.

7. n8n automation workflow

n8n is the automation layer. It receives the sensor readings, stores them, checks thresholds, calls the AI model, and routes the correct alert to Telegram.

Workflow A — real-time sensor processing

mermaid

flowchart TD
    A[ESP32 POST sensor JSON] --> B[Webhook Trigger]
    B --> C[Validate required fields]
    C --> D{Valid payload?}
    D -- No --> E[Log invalid reading]
    D -- Yes --> F[Normalize units and timestamp]
    F --> G[Google Sheets: Append Row]
    F --> H[Evaluate PM thresholds]
    H --> I{Alert condition?}
    I -- No --> J[Store normal status]
    I -- Yes --> K[Create alert event]
    K --> L[Telegram text notification]
    K --> M[Voice message workflow]
    F --> N[Collect recent history]
    N --> O[AI forecast and explanation]
    O --> P[Store forecast result]
    P --> Q[Update dashboard data]

Step-by-step node configuration

Node 1 — Webhook

Set method to POST and path to air-quality. The input JSON should contain device_id, pm1, pm25, pm10, temperature_c and humidity_pct.

Use the production URL only after activating the workflow. The ESP32 firmware above sends this payload.

Node 2 — Validate and normalize

Use an Edit Fields (Set) node or Code node to validate numeric values, attach a timestamp, and reject malformed payloads. Do not silently replace missing sensor values with zero.

Node 3 — Google Sheets

Use the Google Sheets node with the Append Row operation. Map each field to the corresponding spreadsheet column.

Node 4 — IF / Switch

Compare the current PM2.5 reading against configured thresholds, or compare the reading against an applicable local AQI standard. Route normal and alert conditions separately.

Node 5 — AI forecast

Retrieve recent historical measurements, send the structured time series to a forecasting service or AI model, validate its output, and save the result. Use a Code node for mathematical forecasting if you want a reproducible baseline.

Node 6 — Telegram

Configure a Telegram bot credential and use Send Message for text alerts. Use Send Audio or Send Voice for audio delivery, depending on the generated file format and the operation supported by your n8n version.

Example data-validation Code node

Add a Code node after the Webhook node. This example expects the webhook body to be in $json.body and stops processing if any required measurement is invalid.

javascript

const input = $json.body ?? $json;

const fields = [
  "pm1",
  "pm25",
  "pm10",
  "temperature_c",
  "humidity_pct"
];

for (const field of fields) {
  if (
    input[field] === undefined ||
    input[field] === null ||
    !Number.isFinite(Number(input[field]))
  ) {
    throw new Error(`Invalid or missing field: ${field}`);
  }
}

const pm1 = Number(input.pm1);
const pm25 = Number(input.pm25);
const pm10 = Number(input.pm10);
const temp = Number(input.temperature_c);
const humidity = Number(input.humidity_pct);

if (
  pm1 < 0 || pm25 < 0 || pm10 < 0 ||
  humidity < 0 || humidity > 100 ||
  temp < -40 || temp > 85
) {
  throw new Error("Measurement outside configured limits");
}

return [{
  json: {
    device_id: String(input.device_id ?? "unknown"),
    timestamp: new Date().toISOString(),
    pm1,
    pm25,
    pm10,
    temperature_c: temp,
    humidity_pct: humidity
  }
}];

The limits above are data-validation limits, not health thresholds. Real deployments should also reject stale data, impossible sensor combinations, duplicate events, and persistent sensor faults.

8. AI agent and air-quality forecasting

A major project requirement is forecasting, not merely displaying current measurements. To achieve that, maintain a history of readings and use a forecasting method that has been evaluated against actual future measurements.

Three forecasting levels

Level 1 — Trend-based baseline

Compare the latest readings with recent rolling averages, calculate the rate of change, and flag rising pollution. This can run in an n8n Code node without an external AI API.

Level 2 — AI-assisted interpretation

Supply the latest measurements and historical summary to an AI model. Ask it to explain the trend, identify uncertainty, and produce a structured risk assessment. An LLM explanation alone is not a validated numerical forecast.

Level 3 — Trained predictive model

Train a regression or time-series model using historical PM2.5, PM10, temperature, humidity, time of day, and—if available—wind, rainfall and external weather observations. Predict future concentrations and measure error on held-out data.

Forecasting flowchart

mermaid

flowchart TD
    A[Historical sensor data] --> B[Remove invalid readings]
    B --> C[Resample into fixed time intervals]
    C --> D[Build time-series features]
    D --> E[Forecast PM2.5 and PM10]
    E --> F[Calculate prediction uncertainty]
    F --> G[Compare with configured thresholds]
    G --> H[AI generates plain-language explanation]
    H --> I[Store prediction and model version]
    I --> J[Dashboard and Telegram notification]
    J --> K[Compare forecast with later observation]
    K --> L[Calculate model error and improve model]

Example trend-based forecasting code

This is a baseline forecast, not a trained AI model. It predicts PM2.5 using the average change between successive readings. It should be replaced or benchmarked against a proper time-series model before making operational claims.

javascript

function forecastPM25(values, horizonSteps = 3) {
  if (
    values.length < 4 ||
    !values.every(v => Number.isFinite(v) && v >= 0)
  ) {
    throw new Error("At least four valid PM2.5 values required");
  }

  const last = values[values.length - 1];
  const recent = values.slice(-6);

  const differences = [];
  for (let i = 1; i < recent.length; i++) {
    differences.push(recent[i] - recent[i - 1]);
  }

  const meanChange =
    differences.reduce((a, b) => a + b, 0) /
    differences.length;

  const predicted = [];
  let current = last;

  for (let i = 0; i < horizonSteps; i++) {
    current = Math.max(0, current + meanChange);
    predicted.push(Number(current.toFixed(2)));
  }

  return {
    latest_pm25: last,
    mean_change_per_step: Number(meanChange.toFixed(2)),
    horizon_steps: horizonSteps,
    forecast_pm25: predicted,
    method: "recent_mean_difference_baseline"
  };
}

// Example only: replace with actual historical data.
const history = [18, 20, 21, 24, 27, 29];
return [{ json: forecastPM25(history, 3) }];

If readings are sampled every 20 seconds, three steps correspond to only one minute. For a meaningful 30-minute or 1-hour forecast, aggregate measurements into suitable time intervals and collect sufficient historical data.

Agentic AI decision logic

The agent should have defined responsibilities rather than unrestricted control.

AI agent tools

  1. Get latest readings: retrieve the current sensor measurements.

  2. Get history: retrieve the latest 30–100 valid time-series samples.

  3. Forecast pollution: invoke the baseline or trained forecasting service.

  4. Assess risk: compare the forecast against configured policy thresholds.

  5. Generate alert text: produce a concise explanation with values and uncertainty.

  6. Record the decision: save the model version, forecast horizon and action taken.

Example system instruction for the AI node:

text

You are an air-quality monitoring assistant.

Use only the sensor measurements, historical data,
and forecasts provided to you.

1. Summarize current PM2.5 and PM10 measurements.
2. Identify whether recent readings are rising, stable,
   or falling.
3. Explain the forecast horizon and uncertainty.
4. Never invent missing measurements or claim a forecast
   is a confirmed observation.
5. Do not calculate or invent a regulatory AQI category.
6. Return structured JSON with:
   status, trend, forecast_summary, uncertainty,
   recommended_precautions, and reason.
7. Safety-critical alerts are controlled by deterministic
   workflow rules, not by free-form AI decisions.

For an n8n AI Agent node, connect a chat-model node and the appropriate tools supported by your n8n version. If the forecasting tool returns numeric results, pass those results to the agent rather than asking the language model to guess the numbers.

9. AQI calculation and alert thresholds

PM2.5 concentration is not itself an AQI number. AQI depends on the relevant country's calculation method, pollutant breakpoints, averaging period, and rounding rules.

For an India-focused project, select and implement the applicable CPCB National Air Quality Index methodology. Do not label arbitrary PM2.5 thresholds as official Indian AQI categories.

Use two independent paths:

  • Measured-condition alerts: triggered from actual sensor measurements and the applicable rules.

  • Forecast alerts: triggered from predicted future conditions, clearly labeled as predictions.

For a prototype, you can use configurable engineering thresholds to test the automation. Mark them as test thresholds until you have implemented and verified the correct local standard.

A useful notification policy includes a persistence window, hysteresis, cooldown period, and sensor-fault detection. For example, require multiple consecutive valid samples before sending a non-emergency alert, but do not delay a required urgent alert because the AI service is unavailable.

10. Telegram text and voice notification system

This feature makes the project more interactive: when pollution rises or a forecast indicates elevated risk, the system can send a text message and a spoken audio alert.

Telegram setup

  1. Open Telegram and search for @BotFather.

  2. Send /newbot and follow the instructions.

  3. Save the bot token securely.

  4. Open your new bot and send it a message.

  5. Obtain the chat ID using the Telegram Bot API or an n8n Telegram Trigger node.

  6. In n8n, create Telegram credentials and configure the Send Message operation.

Never expose the bot token in frontend JavaScript, a public repository, or a shared spreadsheet.

Voice-alert workflow

mermaid

flowchart TD
    A[Alert event from n8n] --> B[Build alert text]
    B --> C[Text-to-Speech service]
    C --> D[Generate audio file]
    D --> E{Audio generated?}
    E -- Yes --> F[Telegram Send Voice or Audio]
    E -- No --> G[Send text-only fallback]
    F --> H[Log notification result]
    G --> H

The text-to-speech service can be a hosted provider or a self-hosted TTS engine. Configure its output format to match the Telegram operation. A voice message typically uses an appropriate OGG/Opus format; an audio file can use other supported formats.

Example alert message

Air Quality Monitor Bot

AIR QUALITY WARNING

PM2.5: 86 µg/m³

PM10: 124 µg/m³

Temperature: 32.1 °C

Humidity: 61%

Trend: Increasing over recent samples.

Forecast: Possible continued deterioration. Forecast confidence must be assessed using the deployed model.

Illustrative sample values, not live measurements.

Example spoken message:

Air quality warning. The latest PM2.5 reading is 86 micrograms per cubic meter. Recent measurements show an increasing trend. Please check the local air-quality advisory and consider appropriate precautions.

The voice message should distinguish measured readings from predicted values. If the TTS service fails, n8n must still send the text alert.

11. Google Sheets integration

Create a spreadsheet called Air_Quality_Monitoring.

Use the following column structure:

Column

Field

A

Timestamp

B

Device ID

C

PM1.0

D

PM2.5

E

PM10

F

Temperature °C

G

Humidity %

H

Measured AQI, if properly calculated

I

Forecast PM2.5

J

Forecast horizon

K

Alert status

L

Alert action

M

Model version

In n8n:

  1. Add a Google Sheets node after data validation.

  2. Authenticate using Google OAuth2 credentials.

  3. Select the spreadsheet and worksheet.

  4. Choose Append Row.

  5. Map the incoming values to the matching columns.

  6. Add another Append Row operation after forecasting to record the prediction and model metadata.

  7. Optionally create a separate Alert_Log worksheet to track notifications and delivery failures.

Avoid writing an entire row repeatedly for the same device and timestamp. Add a unique event identifier if you need reliable deduplication.

12. ThingSpeak IoT cloud dashboard

ThingSpeak provides time-series storage and charts. The ESP32 firmware sends PM1.0, PM2.5, PM10, temperature and humidity to the channel.

Measuring air quality with Raspberry Pi #4 - First measurements - element14 Community
pro2_plus_receiver - WeatherDuino WiKi
IoT Data Explorer for ThingSpeak and MATLAB - MATLAB & Simulink

Setup instructions

  1. Create a ThingSpeak account and channel.

  2. Enable the required fields.

  3. Copy the channel ID and Write API Key.

  4. Configure the firmware with the Write API Key.

  5. Upload the code and confirm that new entries appear.

  6. Create charts for PM2.5, PM10, temperature and humidity.

  7. Keep the channel private unless public access is intentional.

  8. Use a separate read key or controlled backend for any private dashboard integration.

Documentation: ThingSpeak channel data and charts .

Dashboard design

The web application should display:

Current PM2.5

Live value

µg/m³

PM10 trend

Time series

Historical readings

AI forecast

Future estimate

Show horizon and uncertainty

Alert status

Normal / Warning

Last notification event

13. Live IoT webpage implementation

A simple first version can use HTML, CSS and JavaScript. The following page is a frontend prototype with simulated readings. It does not connect to a real sensor until you replace the demo data source with a backend endpoint or ThingSpeak's read API.

html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>AI Air Quality Monitor</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      max-width: 950px;
      margin: 30px auto;
      padding: 0 16px;
      background: #f4f7fb;
      color: #182230;
    }
    h1 { color: #155e75; }
    .grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
      gap: 14px;
    }
    .card {
      background: white;
      border-radius: 12px;
      padding: 20px;
      box-shadow: 0 2px 10px #0000000c;
    }
    .value { font-size: 30px; font-weight: 700; }
    .muted { color: #64748b; }
  </style>
</head>
<body>
  <h1>AI Air Quality Monitoring</h1>
  <p class="muted" id="updated">Demo mode — not live sensor data</p>

  <div class="grid">
    <div class="card">
      <div>PM2.5 (µg/m³)</div>
      <div class="value" id="pm25">--</div>
    </div>
    <div class="card">
      <div>PM10 (µg/m³)</div>
      <div class="value" id="pm10">--</div>
    </div>
    <div class="card">
      <div>Temperature (°C)</div>
      <div class="value" id="temp">--</div>
    </div>
    <div class="card">
      <div>Humidity (%)</div>
      <div class="value" id="humidity">--</div>
    </div>
  </div>

  <section class="card" style="margin-top:16px">
    <h2>System status</h2>
    <p id="status">Waiting for data</p>
    <p id="forecast">Forecast unavailable</p>
  </section>

  <script>
    // DEMO ONLY. Replace with a real backend request.
    const demo = {
      pm25: 24,
      pm10: 41,
      temp: 30.5,
      humidity: 58
    };

    function render(data) {
      document.getElementById("pm25").textContent = data.pm25;
      document.getElementById("pm10").textContent = data.pm10;
      document.getElementById("temp").textContent = data.temp;
      document.getElementById("humidity").textContent =
        data.humidity;

      document.getElementById("status").textContent =
        "Demo data — AQI category not calculated";

      document.getElementById("updated").textContent =
        "Updated: " + new Date().toLocaleString();

      document.getElementById("forecast").textContent =
        "No trained forecast model connected";
    }

    render(demo);

    // Production pattern:
    // fetch("/api/latest")
    //   .then(r => {
    //     if (!r.ok) throw new Error("API request failed");
    //     return r.json();
    //   })
    //   .then(render)
    //   .catch(() => {
    //     document.getElementById("status").textContent =
    //       "Unable to retrieve live sensor data";
    //   });
  </script>
</body>
</html>

Connecting the webpage to real data

For a production implementation, create a backend endpoint such as /api/latest that reads the most recent sensor record from ThingSpeak or a database. The webpage calls that endpoint periodically.

Recommended production architecture:

  • ESP32 → ThingSpeak and/or n8n webhook.

  • n8n → historical database and Google Sheets.

  • Forecast service → prediction results.

  • Web backend → latest readings, forecasts and alerts.

  • Web frontend → charts, status cards and historical trends.

Do not place private API keys in the webpage's JavaScript. If the ThingSpeak channel is private, use a backend to read it and protect the credentials.

14. Complete workflow map

This is the final combined project architecture.

mermaid

flowchart TB
    subgraph DEVICE["Hardware layer"]
        A[PMS5003]
        B[DHT22]
        C[ESP32]
        A --> C
        B --> C
    end

    subgraph CLOUD["Cloud and automation"]
        D[HTTPS telemetry]
        E[n8n Webhook]
        F[Validation and rules]
        G[Google Sheets]
        H[ThingSpeak]
        I[Forecast model]
        J[AI interpretation]
        K[Telegram text]
        L[Text to speech]
        M[Telegram voice]
    end

    subgraph UI["User interface"]
        N[Live webpage]
        O[Historical charts]
        P[Forecast and alert panel]
    end

    C --> D
    D --> E
    C --> H
    E --> F
    F --> G
    F --> I
    I --> J
    F --> K
    F --> L
    L --> M
    H --> N
    G --> O
    I --> P
    J --> P
    K --> P

15. Testing and validation plan

Do not consider the project complete until each part has been tested independently.

Project test checklist

0 of 12

Reset checklist

Forecast evaluation

Use chronological train/test separation rather than randomly mixing time-series records. Record at least these metrics:

  • MAE: mean absolute error of PM2.5 predictions.

  • RMSE: root mean squared error.

  • Forecast bias: whether the model systematically overpredicts or underpredicts pollution.

  • Alert performance: false alarms, missed events, and time between an event and its notification.

A low-cost sensor is useful for educational and indicative monitoring, but it should not automatically be treated as a regulatory-grade instrument. Compare it with a suitable reference monitor and follow the sensor manufacturer's operating requirements. The US EPA explains the importance of evaluating low-cost air sensors against reference measurements.

US EPA
+1

 

16. Security, reliability and limitations

For a deployment beyond a classroom prototype:

  • Use HTTPS with certificate verification.

  • Protect API keys and bot credentials using environment variables or a secrets manager.

  • Authenticate the n8n webhook, and validate the device identity.

  • Add a persistent offline queue and retry mechanism.

  • Store a timestamp and device ID with every observation.

  • Detect sensor disconnection and stale readings.

  • Add alert cooldowns and event deduplication.

  • Keep urgent threshold-based alerts independent of the AI API.

  • Back up n8n workflows and historical data.

  • Show a visible sensor-data timestamp and forecast uncertainty on the webpage.

Also remember that temperature, humidity and particulate concentration alone may not be sufficient to forecast outdoor air quality accurately. Weather, local emissions, traffic, seasonal effects and nearby sources can matter considerably.

17. Final project deliverables

The finished project should contain these deliverables:

Deliverable

Contents

Hardware prototype

ESP32, PMS5003, DHT22 and optional gas sensor

Circuit documentation

Pin map, wiring diagram and electrical precautions

Firmware

Wi-Fi, sensor parsing, cloud uploads and error handling

n8n workflows

Ingestion, validation, logging, forecasting and alerts

AI forecasting

Baseline model, trained model if available, and evaluation results

Telegram bot

Text alerts and voice notifications

Cloud storage

ThingSpeak channel and Google Sheets log

Web application

Live values, historical charts and forecast panel

Final report

Architecture, implementation, testing and limitations

Suggested report title

“AI-Powered Real-Time Air Quality Monitoring and Forecasting System Using ESP32, Agentic AI, n8n Automation, ThingSpeak, Google Sheets and Telegram Voice Notifications.”

This is a practical design for an academic or prototype project. The firmware and dashboard snippets provide a starting implementation; the full production system still requires configured credentials, a working n8n instance, a text-to-speech service, and a tested forecasting pipeline.

 

## Project Summary

AI-Powered Real-Time Air Quality Monitoring and Forecasting System Using ESP32, IoT, Agentic AI, n8n Automation, Telegram Voice Alerts, Google Sheets and ThingSpeak

This project develops an intelligent IoT system that monitors air quality in real time, forecasts potential pollution trends, and automatically notifies users when air quality deteriorates.

The ESP32 microcontroller collects environmental measurements using a PMS5003 particulate-matter sensor and a DHT22 temperature and humidity sensor. It transmits the data through Wi-Fi to ThingSpeak for cloud monitoring and to n8n for automated processing.

The n8n workflow validates sensor readings, stores historical data in Google Sheets, evaluates alert conditions, and connects to an AI model for forecasting and natural-language explanations. When configured thresholds are exceeded, the system sends Telegram text notifications and voice alerts using a text-to-speech service.

A web dashboard displays current sensor readings, historical trends, forecasts, and alert status.

### Main components

- Hardware: ESP32, PMS5003, DHT22, optional MQ-135 gas sensor.
- IoT cloud: ThingSpeak for sensor data visualization.
- Automation: n8n workflows for data processing and notifications.
- Artificial intelligence: Trend analysis, pollution forecasting and risk explanations.
- Communication: Telegram text and voice alerts.
- Data storage: Google Sheets for historical records.
- Web application: Real-time monitoring dashboard.

### System workflow

mermaid
```

flowchart TD
A[Air Quality Sensors] --> B[ESP32]
B --> C[Wi-Fi and Cloud Upload]
C --> D[ThingSpeak Dashboard]
C --> E[n8n Automation]
E --> F[Google Sheets]
E --> G[AI Forecasting]
G --> H[Risk Assessment]
H --> I[Telegram Text Alerts]
H --> J[Text-to-Speech]
J --> K[Telegram Voice Alerts]
D --> L[IoT Web Dashboard]
G --> L
```

### Expected outcomes

- Continuous air-quality data collection.
- Remote monitoring through a cloud dashboard.
- Early warnings of potentially deteriorating air quality.
- Automated text and voice notifications.
- Historical data analysis and forecast evaluation.
- Reduced need for manual monitoring.

Conclusion: The project integrates IoT hardware, cloud services, AI forecasting, and workflow automation into a unified environmental monitoring solution. It is suitable for an academic project, smart-city prototype, or environmental monitoring demonstration. Accurate forecasting and regulatory AQI reporting require validated models, appropriate sensor calibration, and the applicable official calculation methodology.