0% found this document useful (0 votes)
5 views

IoT Project Report [Air Quality Monitoring System With Arduino]

The document outlines a project proposal for an Air Quality Monitoring System utilizing Arduino technology, aimed at providing real-time air quality data through a cost-effective setup. It details the system architecture, including hardware and software components, and describes the integration of various sensors and APIs for data collection and processing. The project emphasizes user alerts, a dynamic dashboard, and future enhancements for predictive analytics, showcasing its potential for scalable environmental monitoring.

Uploaded by

Arijit Roy
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views

IoT Project Report [Air Quality Monitoring System With Arduino]

The document outlines a project proposal for an Air Quality Monitoring System utilizing Arduino technology, aimed at providing real-time air quality data through a cost-effective setup. It details the system architecture, including hardware and software components, and describes the integration of various sensors and APIs for data collection and processing. The project emphasizes user alerts, a dynamic dashboard, and future enhancements for predictive analytics, showcasing its potential for scalable environmental monitoring.

Uploaded by

Arijit Roy
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

CSE Program

School of Science Engineering & Technology


East Delta University
Chattogram – 4209, Bangladesh

A Project Proposal on
Air Quality Monitoring System with Arduino

Course Name & Code: IoT Based Project Development (CSE 342)

Submitted to:
Dr. Mohammad Mahbubur Rahman
Assistant Professor
School of Science Engineering & Technology
East Delta University
Chattogram – 4209, Bangladesh

Submitted by:

Date of Submission: April 16, 2025


Table of Contents
Section Title Page

1. Abstract 3

2. Introduction 3

3. Objective 3

4. System Architecture Overview 4

4.1. Hardware Architecture 4

4.2. Software Architecture 4

5. Hardware Implementation 4

5.1. Components & Sensors 4

5.2. Circuit & Wiring Details 5

5.3. Arduino Sensor Data Acquisition Code 6

6. Software Implementation 7

6.1. Backend System 7

6.2. API Integration 8

6.3. Alert System & Email Notification 9

6.4. Frontend Dashboard 10

6.5. Data Flow & Full Integration 10

7. Future Enhancement: Air Quality Prediction 11

8. Results & Discussion 11

9. Conclusion 11

10. Reference 12

11. Appendices 12

Appendix A: Arduino Code 12

Appendix B: Backend Code 15

Appendix C: Website Screenshot 19

2
Abstract

This project report documents the design, implementation, and evaluation of a cost-effective, real-
time Air Quality Monitoring System leveraging an Arduino-based sensor network. The system
integrates three MQ-series gas sensors (MQ-7 for CO, MQ-4 for Methane, and MQ-135 for general
air quality) with supplementary environmental data fetched via the OpenWeatherMap API. A
backend built with Node.js and Express aggregates data, computes an aggregate Air Quality Index
(AQI), and interacts with a React-driven dashboard for real-time visualization. Additionally, an
email alert system notifies users based on personalized health conditions.

Introduction

Air quality is a critical environmental parameter directly affecting public health. Traditional air
monitoring systems are often expensive and lack real-time capabilities. With the proliferation of
low-cost sensors and IoT technologies, there is a strong need to design systems that enable
continuous monitoring of air quality both indoors and outdoors. This project is developed as a
multidisciplinary initiative integrating hardware sensor modules with advanced web technologies
to deliver comprehensive, actionable insights into ambient air conditions.

Objectives

• Low-Cost Monitoring: Build a functional hardware prototype using an Arduino Mega and
cost-effective MQ sensors.
• Data Integration: Collect indoor air quality readings and combine them with outdoor
environmental data from the OpenWeatherMap API.
• Real-Time Insights: Calculate an aggregate AQI in real time and display it on both a local
interface and a web-based dashboard.
• User Alerts: Implement an email alert system to notify subscribers with customized health
recommendations based on AQI values.
• Future Prediction: Lay the foundation for predictive analytics using historical data and a
HuggingFace time series transformer model.

3
System Architecture Overview
Hardware Architecture

• Microcontroller: Arduino Mega


• Sensors:
o MQ-7: Carbon Monoxide (CO) sensor
o MQ-4: Methane (CH₄) sensor
o MQ-135: Air quality sensor (detecting various gases)
• Communication: Serial communication via a USB cable connected to COM3 port

Software Architecture

• Backend: Node.js/Express server fetching real-time data from both Arduino and
OpenWeatherMap.
• Database: MongoDB used for historical data storage (if needed for analytics).
• Frontend: React-based dashboard for data visualization and user interaction.
• Alert System: Automated email notifications tailored to health conditions.
• Future Prediction Module: A planned integration with a HuggingFace time series
transformer model for AQI forecasting.

Hardware Implementation
Components & Sensors

• Arduino Mega: Chosen for its multiple I/O pins and enhanced memory capacity.

• Sensors:
o MQ-7: Detects CO levels.

4
o MQ-4: Measures methane concentration.

o MQ-135: Assesses general air quality by detecting gases such as ammonia,


benzene, and CO₂.

• Other Components:
o USB cable for serial communication.
o Breadboard and standard wiring components.

Circuit & Wiring Details

The sensors are connected to specific analog pins of the Arduino as follows:

Component Function Arduino Pin


MQ-7 Carbon Monoxide (CO) A0
MQ-135 General Air Quality Sensor A1
MQ-4 Methane (CH4) Sensor A2

Note: Although an OLED display was originally planned for real-time display, the final
implementation transmits data via serial communication over USB.

5
Arduino Sensor Data Acquisition Code

The Arduino program performs several key functions:

• Reads analog sensor values.


• Calculates gas concentrations in PPM.
• Sends the sensor data via serial in JSON format.
• Receives AQI values and status messages from the server.

Key functions include:

// Function to calculate Methane/CH4 (MQ4)


float calculateCH4ppm(int sensorValue) {
float voltage = sensorValue * (5.0 / 1023.0);
float rs = ((5.0 * 10.0) / voltage) - 10.0; // 10K load resistor
// MQ4 is calibrated for methane
// Rs/R0 = 1 at around 1000ppm CH4 in clean air
// For MQ4, a≈1000, b≈-2.95 (steeper curve)
float r0 = 10.0 * MQ4_RATIO_CLEAN_AIR;
return 1000.0 * pow(rs / r0, -2.95);
}
// Function to calculate air quality (MQ135)
float calculateAirQualityppm(int sensorValue) {

6
float voltage = sensorValue * (5.0 / 1023.0);
float rs = ((5.0 * 10.0) / voltage) - 10.0; // 10K load resistor
// MQ135 is primarily for CO2 and other gases
// Rs/R0 = 1 at 400ppm CO2 in clean air
// For MQ135, a≈400, b≈-2.2
float r0 = 10.0 * MQ135_RATIO_CLEAN_AIR;
return 400.0 * pow(rs / r0, -2.2);
}
// Function to read sensors
void readSensors() {
// Read raw analog values
mq7Value = analogRead(MQ7_PIN);
mq135Value = analogRead(MQ135_PIN);
mq4Value = analogRead(MQ4_PIN);

// Convert to PPM using sensor-specific calculations


co_ppm = calculateCOppm(mq7Value);
ch4_ppm = calculateCH4ppm(mq4Value);
air_quality_ppm = calculateAirQualityppm(mq135Value);

// Apply reasonable limits to prevent extreme values


co_ppm = constrain(co_ppm, 0.1, 1000.0);
ch4_ppm = constrain(ch4_ppm, 500.0, 10000.0);
air_quality_ppm = constrain(air_quality_ppm, 400.0, 5000.0);
}

This snippet highlights the core calculations and JSON data transmission used by the Arduino to
communicate with the server.

Software Implementation
Backend System
The backend system is responsible for two major tasks:

1. Data Aggregation & Processing:


It receives serial data from the Arduino, fetches additional environmental data from the
OpenWeatherMap API, computes an overall AQI, and stores the data if needed.

Serial Communication
The server connects to the Arduino via USB serial communication:
// Connect to Arduino port
(async () => {
try {
const arduinoPort = "COM3";

7
console.log(`Attempting to connect to port: ${arduinoPort}`);
arduinoPortInstance = new SerialPort({
path: arduinoPort,
baudRate: 9600,
autoOpen: false,
});
arduinoPortInstance.open((err) => {
if (err) {
console.error("Serial Port Error:", err.message);
return;
}
console.log("Serial port opened successfully");
const parser = arduinoPortInstance.pipe(new ReadlineParser({ delimiter:
"\r\n" }));
parser.on("data", async (rawData) => {
// Process incoming data from Arduino (parsing and forwarding for API
integration)
});
});
} catch (error) {
console.error("Serial Port Init Error:", error.message);
}
})();

API Integration
The system fetches environmental data from OpenWeatherMap, combining it with
sensor data:
The server has RESTful endpoints to serve the dashboard and trigger email notifications
based on preset thresholds.

// Fetch current environmental data from OpenWeatherMap

const apiKey = process.env.OPENWEATHERMAP_API_KEY;

if (apiKey) {
try {
const [airPollution, weather] = await Promise.all([
axios.get(`https://api.openweathermap.org/data/2.5/air_pollution?lat=22.3569&lon=91.783
2&appid=${apiKey}`),
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=${process.env.CITY_NAME ||
"Chittagong,BD"}&appid=${apiKey}&units=metric`),
]);
const components = airPollution.data.list[0].components;
// Proceed with data processing and storage
} catch (apiError) {

8
console.error("Failed to fetch API data:", apiError.message);
}
}
Data Processing and Storage
The backend combines Arduino data with API data, computes AQI, and stores the record
in MongoDB:
// Create new sensor data entry merging Arduino and API data
const newEntry = new SensorData({
co: parseFloat(sensorData.co) || 0,
methane: parseFloat(sensorData.methane) || 0,
airQuality: parseFloat(sensorData.airQuality) || 0,
temperature: weather.data.main.temp || 0,
humidity: weather.data.main.humidity || 0,
pm25: components.pm2_5 || 0,
pm10: components.pm10 || 0,
o3: components.o3 || 0,
so2: components.so2 || 0,
no2: components.no2 || 0,
nh3: components.nh3 || 0,
aqi: calculateAQI({
pm25: components.pm2_5,
pm10: components.pm10,
o3: components.o3,
co: components.co / 1000, // Approximate conversion from μg/m³ to ppm
so2: components.so2,
no2: components.no2,
nh3: components.nh3,
}),
});
await newEntry.save();
Alert System
A scheduled check uses NodeMailer to send an email alert if air quality thresholds are
exceeded:
// Set up alert check interval (every 2 minutes)
const ALERT_CHECK_INTERVAL = 2 * 60 * 1000;
setInterval(async () => {
try {
const count = await checkAndSendAlerts();
console.log(`Alert check complete: Sent ${count || 0} notifications`);
} catch (error) {
console.error("Alert Check Error:", error);
}
}, ALERT_CHECK_INTERVAL);

9
Frontend Dashboard

The frontend is designed using React to provide users with a dynamic, real-time view of air
quality data. The dashboard fetches data from the backend API periodically and displays:

• Real-Time AQI Value


• Individual Sensor Readings for CO, methane, and general air quality
• Environmental Data: Temperature, humidity, PM2.5, PM10
• Health Suggestions: Based on the AQI value

// Minimal React component for live data display


function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get('/api/sensordata');
setData(res.data);
};
const interval = setInterval(fetchData, 2000);
return () => clearInterval(interval);
}, []);

if (!data) return <div>Loading...</div>;


return (
<div>
<h2>AQI: {data.aqi}</h2>
<p>CO: {data.sensorData.co}</p>
<p>Air Quality: {data.sensorData.airQuality}</p>
<p>Methane: {data.sensorData.methane}</p>
</div>
);
}

Data Flow & API Integration

1. Data Acquisition:
o Sensors on the Arduino read analog signals and send data via serial (USB/COM3).
2. Backend Processing:
o The Node.js server listens to serial port data, fetches supplementary data from the
OpenWeatherMap API, and computes an overall AQI.
3. Data Presentation:
o The REST API serves combined data to the React dashboard.
4. Alert System:
o If the computed AQI exceeds set thresholds, the backend triggers an email alert via
a service (using NodeMailer or similar), sending customized health advice to
subscribed users.

10
Future Enhancements: Air Quality Prediction

As an ambitious extension, the system will incorporate a prediction module using a HuggingFace
time series transformer model. By utilizing the last two years of data (including O₃ and PM2.5
metrics), the model can forecast future air quality trends. This involves:

• Data preprocessing and feature extraction from historical sensor and API data.
• Training a transformer-based model on the time series data.
• Integrating model inference into the backend to display predictions on the dashboard.

Results & Discussion

The implemented project successfully demonstrates the integration of sensor data with external
environmental parameters to calculate an AQI in real time. Key outcomes include:

• Accurate Data Integration:


Real-time fusion of Arduino sensor readings and external API data.
• Responsive Dashboard:
User-friendly interface displaying comprehensive environmental metrics.
• Automated Alerts:
Successful deployment of an email alert system that tailors health recommendations.
• Scalability Potential:
The system’s architecture allows for future enhancements, such as predictive analytics
via the HuggingFace model.

Challenges encountered include sensor calibration and ensuring robust error handling in
asynchronous API calls. Future iterations will focus on refining sensor accuracy and optimizing
prediction algorithms.

Conclusion

This project serves as a proof-of-concept for a low-cost, real-time air quality monitoring system.
By bridging hardware sensor data with external environmental information, the system offers
comprehensive air quality insights and proactive health alerts. The integration of a robust MERN
stack and plans for predictive analytics ensure that the solution is both scalable and adaptable to
future requirements in environmental monitoring.

11
References

• Arduino Documentation and Sensor Data Sheets.


• OpenWeatherMap API Documentation.
• Node.js and Express.js Official Documentation.
• React.js Official Documentation.
• HuggingFace Transformer Models for Time Series Forecasting.

Source Code Repository

The complete source code for this project is available on GitHub. The repository includes all the
Arduino sketches, backend and frontend code, as well as configuration files and documentation.

GitHub Repository: https://github.com/aroyy007/Air_Quality_Monitoring_System.git

Repository Structure:

• /arduino: Contains the Arduino code including sensor reading and JSON data
transmission.
• /backend: Node.js/Express server code that handles serial communication, API
integration, and data processing.
• /frontend: React dashboard code for real-time data visualization.
• /docs: Additional documentation and project resources.

Appendix
Appendix A: Arduino Code

#include <Wire.h>
// Sensor Pins
const int MQ7_PIN = A0; // CO Sensor
const int MQ135_PIN = A1; // Air Quality Sensor
const int MQ4_PIN = A2; // Methane Sensor
// Variables to store sensor readings
int mq7Value = 0;
int mq135Value = 0;
int mq4Value = 0;
float co_ppm = 0;
float ch4_ppm = 0;
float air_quality_ppm = 0;
// Variables for displaying AQI (will be calculated on server)
int aqi = 0;
String airQualityMessage = "Calculating...";

12
// Sensor calibration values (adjust based on datasheet or calibration)
const float MQ7_RATIO_CLEAN_AIR = 9.83;
const float MQ135_RATIO_CLEAN_AIR = 3.6;
const float MQ4_RATIO_CLEAN_AIR = 4.4;
// Timing variables
unsigned long previousMillis = 0;
const long sensorReadInterval = 2000; // Read sensors every 2 seconds
const long serialTransmitInterval = 5000; // Send to PC every 5 seconds

void setup() {
Serial.begin(9600);
// Sensor warm-up period
delay(30000);
// Initial readings to stabilize
for (int i = 0; i < 10; i++) {
readSensors();
delay(1000);
}
}
void loop() {
unsigned long currentMillis = millis();
// Read sensor values at specified interval
if (currentMillis - previousMillis >= sensorReadInterval) {
previousMillis = currentMillis;
readSensors();
// Send data to PC at specified interval
if (currentMillis % serialTransmitInterval < sensorReadInterval) {
sendDataToPC();
}
}
// Check if there's data from the server (for AQI feedback)
receiveFromServer();
}
// Function to calculate CO (MQ7)
float calculateCOppm(int sensorValue) {
float voltage = sensorValue * (5.0 / 1023.0);
float rs = ((5.0 * 10.0) / voltage) - 10.0; // 10K load resistor
float r0 = 10.0 * MQ7_RATIO_CLEAN_AIR;
return 100.0 * pow(rs / r0, -1.5);
}
// Function to calculate Methane/CH4 (MQ4)
float calculateCH4ppm(int sensorValue) {
float voltage = sensorValue * (5.0 / 1023.0);
float rs = ((5.0 * 10.0) / voltage) - 10.0;
float r0 = 10.0 * MQ4_RATIO_CLEAN_AIR;

13
return 1000.0 * pow(rs / r0, -2.95);
}
// Function to calculate air quality (MQ135)
float calculateAirQualityppm(int sensorValue) {
float voltage = sensorValue * (5.0 / 1023.0);
float rs = ((5.0 * 10.0) / voltage) - 10.0;
float r0 = 10.0 * MQ135_RATIO_CLEAN_AIR;
return 400.0 * pow(rs / r0, -2.2);
}
void readSensors() { // Function to read sensors
mq7Value = analogRead(MQ7_PIN);
mq135Value = analogRead(MQ135_PIN);
mq4Value = analogRead(MQ4_PIN);
co_ppm = calculateCOppm(mq7Value);
ch4_ppm = calculateCH4ppm(mq4Value);
air_quality_ppm = calculateAirQualityppm(mq135Value);
co_ppm = constrain(co_ppm, 0.1, 1000.0);
ch4_ppm = constrain(ch4_ppm, 500.0, 10000.0);
air_quality_ppm = constrain(air_quality_ppm, 400.0, 5000.0);
}
void sendDataToPC() { // Function to send data to PC/server
Serial.print(F("{\"co\":"));
Serial.print(co_ppm, 1);
Serial.print(F(",\"methane\":"));
Serial.print(ch4_ppm, 1);
Serial.print(F(",\"airQuality\":"));
Serial.print(air_quality_ppm, 1);
float estimated_pm25 = air_quality_ppm * 0.3;
float estimated_pm10 = air_quality_ppm * 0.5;
Serial.print(F(",\"pm25\":"));
Serial.print(estimated_pm25, 1);
Serial.print(F(",\"pm10\":"));
Serial.print(estimated_pm10, 1);
Serial.println(F("}"));
}
void receiveFromServer() { // Function to receive and parse data from server
if (Serial.available() > 0) {
String data = Serial.readStringUntil('\n');
if (data.startsWith("{") && data.indexOf("aqi") > 0) {
int aqiStart = data.indexOf("aqi") + 5;
int aqiEnd = data.indexOf(",", aqiStart);
if (aqiEnd < 0) aqiEnd = data.indexOf("}", aqiStart);
String aqiStr = data.substring(aqiStart, aqiEnd);
aqi = aqiStr.toInt();
int statusStart = data.indexOf("status") + 9;

14
if (statusStart > 9) {
int statusEnd = data.indexOf("\"", statusStart);
if (statusEnd > statusStart) {
airQualityMessage = data.substring(statusStart, statusEnd);
}
}
}
}
}
Appendix B: Backend Server-Side Code
import express from "express";
import dotenv from "dotenv";
import cors from "cors";
import connectDB from "./config/db.js";
import sensorRoutes from "./routes/sensorRoutes.js";
import weatherRoutes from "./routes/weatherRoutes.js";
import emailRoutes from "./routes/emailRoutes.js";
import predictionRoutes from "./routes/predictionRoutes.js";
import { SerialPort } from "serialport";
import { ReadlineParser } from "@serialport/parser-readline";
import SensorData from "./models/SensorData.js";
import { checkAndSendAlerts } from "./controllers/emailController.js";
import axios from "axios";
import { calculateAQI } from "./utils/aqiCalculator.js";
dotenv.config();
const app = express();
app.use(express.json());
app.use(cors({ origin: "http://localhost:8080" }));
connectDB();
app.use("/api/sensors", sensorRoutes);
app.use("/api/weather", weatherRoutes);
app.use("/api/alerts", emailRoutes);
app.use("/api/predictions", predictionRoutes);
app.get("/", (req, res) => res.send("API is running..."));
let arduinoPortInstance = null;
(async () => {
try {
const arduinoPort = "COM3";
console.log(`Connecting to Arduino at: ${arduinoPort}`);
arduinoPortInstance = new SerialPort({
path: arduinoPort,
baudRate: 9600,
autoOpen: false,
});
arduinoPortInstance.open((err) => {

15
if (err) {
console.error("Serial Port Error:", err.message);
return;
}
console.log("Serial port opened");
const parser = arduinoPortInstance.pipe(new ReadlineParser({ delimiter: "\r\n" }));
parser.on("data", async (rawData) => {
try {
const trimmedData = rawData.trim();
if (!trimmedData) return;

const sensorData = JSON.parse(trimmedData);


console.log("Arduino Data:", sensorData);

const apiKey = process.env.OPENWEATHERMAP_API_KEY;


if (apiKey) {
try {
const [airPollution, weather] = await Promise.all([
axios.get(`https://api.openweathermap.org/data/2.5/air_polluti
on?lat=22.3569&lon=91.7832&appid=${apiKey}`),
axios.get(`https://api.openweathermap.org/data/2.5/weather?q=$
{process.env.CITY_NAME || "Chittagong,BD"}&appid=${apiKey}&units=metric`)
]);
const components = airPollution.data.list[0].components;
const newEntry = new SensorData({
co: parseFloat(sensorData.co) || 0,
methane: parseFloat(sensorData.methane) || 0,
airQuality: parseFloat(sensorData.airQuality) || 0,
temperature: weather.data.main.temp || 0,
humidity: weather.data.main.humidity || 0,
pm25: components.pm2_5 || 0,
pm10: components.pm10 || 0,
o3: components.o3 || 0,
so2: components.so2 || 0,
no2: components.no2 || 0,
nh3: components.nh3 || 0,
aqi: calculateAQI({
pm25: components.pm2_5,
pm10: components.pm10,
o3: components.o3,
co: components.co / 1000,
so2: components.so2,
no2: components.no2,
nh3: components.nh3,
}),

16
});
await newEntry.save();
console.log("Saved data to DB");
checkAndSendAlerts();
} catch {
await saveArduinoOnly(sensorData);
}
} else {
await saveArduinoOnly(sensorData);
}
} catch (error) {
console.error("Data Handling Error:", error.message);
}
});
arduinoPortInstance.on("error", (err) => {
console.error("Serial Port Error:", err.message);
});
arduinoPortInstance.on("close", () => {
console.log("Serial port closed. Reconnecting...");
setTimeout(() => arduinoPortInstance.open(), 5000);

} catch (error) {
console.error("Serial Init Error:", error.message);
}
})();

async function saveArduinoOnly(sensorData) {


const newEntry = new SensorData({
co: parseFloat(sensorData.co) || 0,
methane: parseFloat(sensorData.methane) || 0,
airQuality: parseFloat(sensorData.airQuality) || 0,
aqi: 0,
temperature: 0,
humidity: 0,
pm25: 0,
pm10: 0,
o3: 0,
so2: 0,
no2: 0,
nh3: 0,
});

await newEntry.save();
console.log("Saved Arduino-only data");
checkAndSendAlerts();

17
}

const ALERT_CHECK_INTERVAL = 2 * 60 * 1000;


setInterval(async () => {
try {
await checkAndSendAlerts();
} catch (error) {
console.error("Alert Check Error:", error);
}
}, ALERT_CHECK_INTERVAL);

(async () => {
try {
await checkAndSendAlerts();
} catch (error) {
console.error("Initial Alert Check Error:", error);
}
})();

const PORT = process.env.PORT || 5001;


app.listen(PORT, () => console.log(`✅ Server running on port ${PORT}`));

18
Appendix C: Website Screenshot

19

You might also like