From 70395d31a6a33d81999237dfda33463c31b9e960 Mon Sep 17 00:00:00 2001 From: Hickmeister <35031453+Hickmeister@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:11:52 +0000 Subject: [PATCH] Updated for temp and humidity --- AirQuality.ino | 314 ++++++++++++++++++++---------- AirQuality/AirQuality.ino | 388 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 602 insertions(+), 100 deletions(-) create mode 100644 AirQuality/AirQuality.ino diff --git a/AirQuality.ino b/AirQuality.ino index 2971711..c040ec1 100644 --- a/AirQuality.ino +++ b/AirQuality.ino @@ -1,35 +1,86 @@ #include #include #include "DFRobot_ENS160.h" +#include "Adafruit_AHTX0.h" // Wi-Fi credentials -const char* ssid = "MegMan-WiFi"; -const char* password = "Zp5egGamPcENXr"; +const char* ssid = "HickmanWiFi"; +const char* password = "BlackBriar8787@@"; // I2C Communication setup -DFRobot_ENS160_I2C ENS160(&Wire, 0x53); // Default I2C address is 0x53 +DFRobot_ENS160_I2C ENS160(&Wire, 0x53); // ENS160 I2C address +Adafruit_AHTX0 aht; // AHTX0 instance AsyncWebServer server(80); -String jsonData = "[]"; // JSON buffer to hold the air quality data -uint8_t lastAQI = 0; // Store the last AQI value for display +// Buffer to store historical data +struct SensorData { + time_t timestamp; // Use NTP time + uint16_t tvoc; + uint16_t eco2; + uint8_t aqi; + float temperature; + float humidity; +}; + +const int BUFFER_SIZE = 60; // Store 60 entries +SensorData sensorBuffer[BUFFER_SIZE]; +int bufferIndex = 0; + +// Function to sync time with NTP server +void syncTime() { + configTime(0, 0, "time.nist.gov", "pool.ntp.org"); + Serial.println("Waiting for time synchronization..."); + while (!time(nullptr)) { + delay(1000); + Serial.print("."); + } + Serial.println("\nTime synchronized!"); +} + +// Function to format the time into a human-readable string +String formatTime(time_t rawTime) { + char buffer[30]; + struct tm* timeInfo = localtime(&rawTime); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeInfo); + return String(buffer); +} + +// Function to calibrate ENS160 using AHTX0 readings +void calibrateENS160() { + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + + float temperature = tempEvent.temperature; + float humidity = humidityEvent.relative_humidity; + + ENS160.setTempAndHum(temperature, humidity); + Serial.printf("Calibrated ENS160 with Temp: %.1f°C, Hum: %.1f%%\n", temperature, humidity); +} + +void prepopulateBuffer() { + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + uint16_t TVOC = ENS160.getTVOC(); + uint16_t ECO2 = ENS160.getECO2(); + uint8_t AQI = ENS160.getAQI(); + + for (int i = 0; i < BUFFER_SIZE; i++) { + sensorBuffer[i] = { + time(nullptr), + TVOC, + ECO2, + AQI, + tempEvent.temperature, + humidityEvent.relative_humidity + }; + } + bufferIndex = BUFFER_SIZE; +} 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) { @@ -40,6 +91,30 @@ void setup() { Serial.print("ESP32 IP Address: "); Serial.println(WiFi.localIP()); + // Sync time using NTP + syncTime(); + + // Initialize AHTX0 sensor + if (!aht.begin()) { + Serial.println("AHTX0 initialization failed. Check the connection!"); + while (1) delay(1000); + } + Serial.println("AHTX0 initialized successfully."); + + // Initialize ENS160 sensor + while (NO_ERR != ENS160.begin()) { + Serial.println("Communication with ENS160 failed. Please check connections."); + delay(3000); + } + Serial.println("ENS160 initialized successfully."); + ENS160.setPWRMode(ENS160_STANDARD_MODE); + + // Calibrate ENS160 with AHTX0 readings + calibrateENS160(); + + // Prepopulate the buffer with initial readings + prepopulateBuffer(); + // Route for the main webpage server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { String html = R"rawliteral( @@ -60,39 +135,33 @@ void setup() { margin-top: 20px; } .card { - background: #1e1e1e; border: 1px solid #333333; } .card.excellent { background-color: #4CAF50; + color: #ffffff; } .card.good { background-color: #8BC34A; + color: #ffffff; } .card.moderate { background-color: #ffa007; color: #000000; } .card.poor { - background-color: #ff4722; + background-color: #ff5722; + color: #ffffff; } .card.unhealthy { - background-color: #de36f4; - } - .apexcharts-canvas { - background: #1e1e1e; + background-color: #f44336; + color: #ffffff; } .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; - } @@ -103,7 +172,7 @@ void setup() {
Air Quality Index (AQI)
-

Value: Loading...

+

Loading...

Loading recommendation...

@@ -111,8 +180,8 @@ void setup() {
-
eCO2 Recommendation
-

Value: Loading...

+
eCO2 (ppm)
+

Loading...

Loading recommendation...

@@ -120,13 +189,31 @@ void setup() {
-
TVOC Recommendation
-

Value: Loading...

+
TVOC (ppb)
+

Loading...

Loading recommendation...

+
+
+
+
+
Temperature (°C)
+

Loading...

+
+
+
+
+
+
+
Humidity (%)
+

Loading...

+
+
+
+
@@ -143,37 +230,18 @@ void setup() { chart: { type: 'line', height: 350, - animations: { - enabled: true, - easing: 'linear', - dynamicAnimation: { speed: 1000 } - }, - toolbar: { show: false }, - zoom: { enabled: false }, + animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } }, background: '#1e1e1e' }, series: [ { name: 'TVOC (ppb)', data: [] }, - { name: 'eCO2 (ppm)', data: [] } + { name: 'eCO2 (ppm)', data: [] }, + { name: 'Temperature (°C)', data: [] }, + { name: 'Humidity (%)', 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' } } + xaxis: { type: 'datetime', labels: { style: { colors: '#ffffff' } } }, + yaxis: { labels: { style: { colors: '#ffffff' } } }, + tooltip: { theme: 'dark' }, }; let chart = new ApexCharts(document.querySelector("#chart"), chartOptions); @@ -183,33 +251,35 @@ void setup() { 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]); + console.log(data.latest.humidity); + + updateCard("aqiCard", data.latest.aqi, getAQIRecommendation(data.latest.aqi)); + updateCard("eco2Card", data.latest.eco2, getECO2Recommendation(data.latest.eco2)); + updateCard("tvocCard", data.latest.tvoc, getTVOCRecommendation(data.latest.tvoc)); + updateCard("temperatureCard", data.latest.temperature, getTemperatureClass(data.latest.temperature)); + updateCard("humidityCard", data.latest.humidity, getHumidityClass(data.latest.humidity)); chart.updateSeries([ - { name: 'TVOC (ppb)', data: tvocData }, - { name: 'eCO2 (ppm)', data: eco2Data } + { name: 'TVOC (ppb)', data: data.history.tvoc }, + { name: 'eCO2 (ppm)', data: data.history.eco2 }, + { name: 'Temperature (°C)', data: data.history.temperature }, + { name: 'Humidity (%)', data: data.history.humidity } ]); - - 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 valueText = card.querySelector(".card-body h4"); 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}`; + valueText.textContent = value.toFixed(1); 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 === 3) return { class: "moderate", text: "Strengthen ventilation." }; if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." }; return { class: "unhealthy", text: "Avoid staying long; ventilate." }; } @@ -229,46 +299,90 @@ void setup() { return { class: "excellent", text: "No effects on health." }; } - setInterval(fetchData, 1000); // Update data every second + function getTemperatureClass(temp) { + if (temp < 18) return { class: "moderate", text: "Too cold, consider heating." }; + if (temp > 26) return { class: "poor", text: "Too hot, ventilate or cool down." }; + return { class: "good", text: "Comfortable temperature." }; + } + + function getHumidityClass(hum) { + if (hum < 30) return { class: "moderate", text: "Too dry, consider humidifying." }; + if (hum > 60) return { class: "poor", text: "Too humid, consider dehumidifying." }; + return { class: "good", text: "Comfortable humidity." }; + } + + setInterval(fetchData, 1000); - - )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); + uint16_t TVOC = ENS160.getTVOC(); + uint16_t ECO2 = ENS160.getECO2(); + uint8_t AQI = ENS160.getAQI(); + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + + sensorBuffer[bufferIndex] = { + time(nullptr), // Use current NTP time + TVOC, + ECO2, + AQI, + tempEvent.temperature, + humidityEvent.relative_humidity + }; + bufferIndex = (bufferIndex + 1) % BUFFER_SIZE; + + String json = "{\"latest\":{"; + json += "\"tvoc\":" + String(TVOC) + ","; + json += "\"eco2\":" + String(ECO2) + ","; + json += "\"aqi\":" + String(AQI) + ","; + json += "\"temperature\":" + String(tempEvent.temperature) + ","; + json += "\"humidity\":" + String(humidityEvent.relative_humidity); + json += "},\"history\":{"; + json += "\"tvoc\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].tvoc) + "]"; + } + json += "],\"eco2\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].eco2) + "]"; + } + json += "],\"temperature\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].temperature) + "]"; + } + json += "],\"humidity\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].humidity) + "]"; + } + json += "]}}"; + + request->send(200, "application/json", json); }); - // Start the server server.begin(); } void loop() { - uint16_t TVOC = ENS160.getTVOC(); - uint16_t ECO2 = ENS160.getECO2(); - uint8_t AQI = ENS160.getAQI(); + // Periodically calibrate the ENS160 sensor + calibrateENS160(); - 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 + // Delay for the main loop + delay(15000); // Calibrate every 10 seconds } diff --git a/AirQuality/AirQuality.ino b/AirQuality/AirQuality.ino new file mode 100644 index 0000000..c040ec1 --- /dev/null +++ b/AirQuality/AirQuality.ino @@ -0,0 +1,388 @@ +#include +#include +#include "DFRobot_ENS160.h" +#include "Adafruit_AHTX0.h" + +// Wi-Fi credentials +const char* ssid = "HickmanWiFi"; +const char* password = "BlackBriar8787@@"; + +// I2C Communication setup +DFRobot_ENS160_I2C ENS160(&Wire, 0x53); // ENS160 I2C address +Adafruit_AHTX0 aht; // AHTX0 instance + +AsyncWebServer server(80); + +// Buffer to store historical data +struct SensorData { + time_t timestamp; // Use NTP time + uint16_t tvoc; + uint16_t eco2; + uint8_t aqi; + float temperature; + float humidity; +}; + +const int BUFFER_SIZE = 60; // Store 60 entries +SensorData sensorBuffer[BUFFER_SIZE]; +int bufferIndex = 0; + +// Function to sync time with NTP server +void syncTime() { + configTime(0, 0, "time.nist.gov", "pool.ntp.org"); + Serial.println("Waiting for time synchronization..."); + while (!time(nullptr)) { + delay(1000); + Serial.print("."); + } + Serial.println("\nTime synchronized!"); +} + +// Function to format the time into a human-readable string +String formatTime(time_t rawTime) { + char buffer[30]; + struct tm* timeInfo = localtime(&rawTime); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeInfo); + return String(buffer); +} + +// Function to calibrate ENS160 using AHTX0 readings +void calibrateENS160() { + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + + float temperature = tempEvent.temperature; + float humidity = humidityEvent.relative_humidity; + + ENS160.setTempAndHum(temperature, humidity); + Serial.printf("Calibrated ENS160 with Temp: %.1f°C, Hum: %.1f%%\n", temperature, humidity); +} + +void prepopulateBuffer() { + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + uint16_t TVOC = ENS160.getTVOC(); + uint16_t ECO2 = ENS160.getECO2(); + uint8_t AQI = ENS160.getAQI(); + + for (int i = 0; i < BUFFER_SIZE; i++) { + sensorBuffer[i] = { + time(nullptr), + TVOC, + ECO2, + AQI, + tempEvent.temperature, + humidityEvent.relative_humidity + }; + } + bufferIndex = BUFFER_SIZE; +} + +void setup() { + Serial.begin(115200); + + // 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()); + + // Sync time using NTP + syncTime(); + + // Initialize AHTX0 sensor + if (!aht.begin()) { + Serial.println("AHTX0 initialization failed. Check the connection!"); + while (1) delay(1000); + } + Serial.println("AHTX0 initialized successfully."); + + // Initialize ENS160 sensor + while (NO_ERR != ENS160.begin()) { + Serial.println("Communication with ENS160 failed. Please check connections."); + delay(3000); + } + Serial.println("ENS160 initialized successfully."); + ENS160.setPWRMode(ENS160_STANDARD_MODE); + + // Calibrate ENS160 with AHTX0 readings + calibrateENS160(); + + // Prepopulate the buffer with initial readings + prepopulateBuffer(); + + // Route for the main webpage + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + String html = R"rawliteral( + + + + + + Air Quality Monitoring + + + + + +
+

Air Quality Monitoring

+
+
+
+
+
Air Quality Index (AQI)
+

Loading...

+

Loading recommendation...

+
+
+
+
+
+
+
eCO2 (ppm)
+

Loading...

+

Loading recommendation...

+
+
+
+
+
+
+
TVOC (ppb)
+

Loading...

+

Loading recommendation...

+
+
+
+
+
+
+
+
+
Temperature (°C)
+

Loading...

+
+
+
+
+
+
+
Humidity (%)
+

Loading...

+
+
+
+
+
+
+
+
+
Real-Time Air Quality Graph
+
+
+
+
+
+
+ + + + )rawliteral"; + request->send(200, "text/html", html); + }); + + server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request) { + uint16_t TVOC = ENS160.getTVOC(); + uint16_t ECO2 = ENS160.getECO2(); + uint8_t AQI = ENS160.getAQI(); + sensors_event_t tempEvent, humidityEvent; + aht.getEvent(&humidityEvent, &tempEvent); + + sensorBuffer[bufferIndex] = { + time(nullptr), // Use current NTP time + TVOC, + ECO2, + AQI, + tempEvent.temperature, + humidityEvent.relative_humidity + }; + bufferIndex = (bufferIndex + 1) % BUFFER_SIZE; + + String json = "{\"latest\":{"; + json += "\"tvoc\":" + String(TVOC) + ","; + json += "\"eco2\":" + String(ECO2) + ","; + json += "\"aqi\":" + String(AQI) + ","; + json += "\"temperature\":" + String(tempEvent.temperature) + ","; + json += "\"humidity\":" + String(humidityEvent.relative_humidity); + json += "},\"history\":{"; + json += "\"tvoc\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].tvoc) + "]"; + } + json += "],\"eco2\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].eco2) + "]"; + } + json += "],\"temperature\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].temperature) + "]"; + } + json += "],\"humidity\":["; + for (int i = 0; i < BUFFER_SIZE; i++) { + int index = (bufferIndex + i) % BUFFER_SIZE; + if (sensorBuffer[index].timestamp == 0) continue; + if (i > 0) json += ","; + json += "[\"" + formatTime(sensorBuffer[index].timestamp) + "\"," + String(sensorBuffer[index].humidity) + "]"; + } + json += "]}}"; + + request->send(200, "application/json", json); + }); + + server.begin(); +} + +void loop() { + // Periodically calibrate the ENS160 sensor + calibrateENS160(); + + // Delay for the main loop + delay(15000); // Calibrate every 10 seconds +}