Files
ESP32_Air_Quality/AirQuality.ino
2025-01-19 17:14:37 +00:00

275 lines
8.4 KiB
C++

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include "DFRobot_ENS160.h"
// Wi-Fi credentials
const char* ssid = "MegMan-WiFi";
const char* password = "Zp5egGamPcENXr";
// I2C Communication setup
DFRobot_ENS160_I2C ENS160(&Wire, 0x53); // Default I2C address is 0x53
AsyncWebServer server(80);
String jsonData = "[]"; // JSON buffer to hold the air quality data
uint8_t lastAQI = 0; // Store the last AQI value for display
void setup() {
Serial.begin(115200);
// Initialise the sensor
while (NO_ERR != ENS160.begin()) {
Serial.println("Communication with ENS160 failed. Please check connections.");
delay(3000);
}
Serial.println("ENS160 initialised successfully.");
// Set power mode
ENS160.setPWRMode(ENS160_STANDARD_MODE);
// Set temperature and humidity compensation
ENS160.setTempAndHum(25.0, 50.0); // Example values: 25°C, 50% humidity
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to Wi-Fi...");
}
Serial.println("Connected to Wi-Fi!");
Serial.print("ESP32 IP Address: ");
Serial.println(WiFi.localIP());
// Route for the main webpage
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
String html = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Air Quality Monitoring</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<style>
body {
background-color: #121212;
color: #f4f4f4;
}
.container {
margin-top: 20px;
}
.card {
background: #1e1e1e;
border: 1px solid #333333;
}
.card.excellent {
background-color: #4CAF50;
}
.card.good {
background-color: #8BC34A;
}
.card.moderate {
background-color: #ffa007;
color: #000000;
}
.card.poor {
background-color: #ff4722;
}
.card.unhealthy {
background-color: #de36f4;
}
.apexcharts-canvas {
background: #1e1e1e;
}
.apexcharts-tooltip {
background: #333333 !important;
color: #ffffff !important;
border: 1px solid #333333 !important;
}
.dark-mode .card {
background: #1e1e1e;
}
.dark-mode .card-title, .dark-mode p {
color: #bebcbc;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-center">Air Quality Monitoring</h1>
<div class="row mt-4">
<div class="col-md-4">
<div id="aqiCard" class="card text-center">
<div class="card-body">
<h5 class="card-title">Air Quality Index (AQI)</h5>
<h4 id="aqiValue">Value: Loading...</h4>
<p id="aqiRecommendation">Loading recommendation...</p>
</div>
</div>
</div>
<div class="col-md-4">
<div id="eco2Card" class="card text-center">
<div class="card-body">
<h5 class="card-title">eCO2 Recommendation</h5>
<h4 id="eco2Value">Value: Loading...</h4>
<p id="eco2Recommendation">Loading recommendation...</p>
</div>
</div>
</div>
<div class="col-md-4">
<div id="tvocCard" class="card text-center">
<div class="card-body">
<h5 class="card-title">TVOC Recommendation</h5>
<h4 id="tvocValue">Value: Loading...</h4>
<p id="tvocRecommendation">Loading recommendation...</p>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-12">
<div class="card dark-mode">
<div class="card-body">
<h5 class="card-title">Real-Time Air Quality Graph</h5>
<div id="chart"></div>
</div>
</div>
</div>
</div>
</div>
<script>
let chartOptions = {
chart: {
type: 'line',
height: 350,
animations: {
enabled: true,
easing: 'linear',
dynamicAnimation: { speed: 1000 }
},
toolbar: { show: false },
zoom: { enabled: false },
background: '#1e1e1e'
},
series: [
{ name: 'TVOC (ppb)', data: [] },
{ name: 'eCO2 (ppm)', data: [] }
],
xaxis: {
type: 'datetime',
labels: { style: { colors: '#ffffff' } },
range: 60000 // 1-minute range
},
yaxis: {
labels: {
style: { colors: '#ffffff' },
formatter: function (val) { return Math.round(val); }
}
},
tooltip: {
theme: 'dark'
},
grid: { borderColor: '#333333' },
stroke: { curve: 'smooth' },
dataLabels: { enabled: false },
legend: { labels: { colors: '#ffffff' } }
};
let chart = new ApexCharts(document.querySelector("#chart"), chartOptions);
chart.render();
async function fetchData() {
const response = await fetch('/data');
const data = await response.json();
const tvocData = data.map(item => [item.timestamp, item.tvoc]);
const eco2Data = data.map(item => [item.timestamp, item.eco2]);
chart.updateSeries([
{ name: 'TVOC (ppb)', data: tvocData },
{ name: 'eCO2 (ppm)', data: eco2Data }
]);
const latest = data[data.length - 1];
updateCard("aqiCard", latest.aqi, getAQIRecommendation(latest.aqi));
updateCard("eco2Card", latest.eco2, getECO2Recommendation(latest.eco2));
updateCard("tvocCard", latest.tvoc, getTVOCRecommendation(latest.tvoc));
}
function updateCard(cardId, value, recommendation) {
const card = document.getElementById(cardId);
const recText = card.querySelector(".card-body p");
const valueText = card.querySelector(".card-body h4:nth-of-type(1)");
card.className = `card text-center ${recommendation.class}`;
valueText.textContent = `${value}`;
recText.textContent = recommendation.text;
}
function getAQIRecommendation(aqi) {
if (aqi === 1) return { class: "excellent", text: "Suitable for long-term living." };
if (aqi === 2) return { class: "good", text: "Maintain adequate ventilation." };
if (aqi === 3) return { class: "moderate", text: "Strengthen ventilation, close to water sources." };
if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." };
return { class: "unhealthy", text: "Avoid staying long; ventilate." };
}
function getECO2Recommendation(eco2) {
if (eco2 > 1500) return { class: "unhealthy", text: "Serious pollution, ventilate!" };
if (eco2 > 1000) return { class: "poor", text: "Polluted air, ventilation recommended." };
if (eco2 > 800) return { class: "moderate", text: "Consider ventilation." };
if (eco2 > 600) return { class: "good", text: "Air quality is fine." };
return { class: "excellent", text: "Air quality is excellent." };
}
function getTVOCRecommendation(tvoc) {
if (tvoc > 6000) return { class: "unhealthy", text: "Headaches and nerve issues possible." };
if (tvoc > 750) return { class: "poor", text: "May cause headaches, ventilate." };
if (tvoc > 50) return { class: "moderate", text: "Some discomfort possible." };
return { class: "excellent", text: "No effects on health." };
}
setInterval(fetchData, 1000); // Update data every second
</script>
</body>
</html>
)rawliteral";
request->send(200, "text/html", html);
});
// Route for sensor data (JSON)
server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "application/json", jsonData);
});
// Start the server
server.begin();
}
void loop() {
uint16_t TVOC = ENS160.getTVOC();
uint16_t ECO2 = ENS160.getECO2();
uint8_t AQI = ENS160.getAQI();
unsigned long timestamp = millis();
String newEntry = "{\"timestamp\":" + String(timestamp) + ",\"tvoc\":" + String(TVOC) + ",\"eco2\":" + String(ECO2) + ",\"aqi\":" + String(AQI) + "}";
if (jsonData == "[]") {
jsonData = "[" + newEntry + "]";
} else {
jsonData = jsonData.substring(0, jsonData.length() - 1) + "," + newEntry + "]";
}
// Limit data buffer to 60 entries (last minute)
int maxEntries = 60;
int count = std::count(jsonData.begin(), jsonData.end(), '{');
if (count > maxEntries) {
int firstComma = jsonData.indexOf(',');
jsonData = "[" + jsonData.substring(firstComma + 1);
}
delay(1000); // Update every second
}