Updated
This commit is contained in:
152
AirQuality.ino
152
AirQuality.ino
@@ -93,6 +93,7 @@ void setup() {
|
|||||||
|
|
||||||
// Sync time using NTP
|
// Sync time using NTP
|
||||||
syncTime();
|
syncTime();
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
// Initialize AHTX0 sensor
|
// Initialize AHTX0 sensor
|
||||||
if (!aht.begin()) {
|
if (!aht.begin()) {
|
||||||
@@ -150,11 +151,11 @@ void setup() {
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
.card.poor {
|
.card.poor {
|
||||||
background-color: #ff5722;
|
background-color:rgb(255, 60, 34);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.card.unhealthy {
|
.card.unhealthy {
|
||||||
background-color: #f44336;
|
background-color:rgb(244, 54, 244);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.apexcharts-tooltip {
|
.apexcharts-tooltip {
|
||||||
@@ -225,94 +226,121 @@ void setup() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
let chartOptions = {
|
let chartOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
height: 350,
|
height: 350,
|
||||||
animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } },
|
animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } },
|
||||||
background: '#1e1e1e'
|
background: '#1e1e1e'
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'TVOC (ppb)', data: [] },
|
{ name: 'TVOC (ppb)', data: [] },
|
||||||
{ name: 'eCO2 (ppm)', data: [] },
|
{ name: 'eCO2 (ppm)', data: [] },
|
||||||
{ name: 'Temperature (°C)', data: [] },
|
{ name: 'Temperature (°C)', data: [] },
|
||||||
{ name: 'Humidity (%)', data: [] }
|
{ name: 'Humidity (%)', data: [] }
|
||||||
],
|
],
|
||||||
xaxis: { type: 'datetime', labels: { style: { colors: '#ffffff' } } },
|
xaxis: { type: 'datetime', labels: { style: { colors: '#ffffff' } } },
|
||||||
yaxis: { labels: { style: { colors: '#ffffff' } } },
|
yaxis: { labels: { style: { colors: '#ffffff' } } },
|
||||||
tooltip: { theme: 'dark' },
|
tooltip: { theme: 'dark' }
|
||||||
};
|
};
|
||||||
|
|
||||||
let chart = new ApexCharts(document.querySelector("#chart"), chartOptions);
|
let chart = new ApexCharts(document.querySelector("#chart"), chartOptions);
|
||||||
chart.render();
|
chart.render();
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
try {
|
||||||
const response = await fetch('/data');
|
const response = await fetch('/data');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to fetch data: ${response.statusText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
console.log(data.latest.humidity);
|
// Update cards
|
||||||
|
|
||||||
updateCard("aqiCard", data.latest.aqi, getAQIRecommendation(data.latest.aqi));
|
updateCard("aqiCard", data.latest.aqi, getAQIRecommendation(data.latest.aqi));
|
||||||
updateCard("eco2Card", data.latest.eco2, getECO2Recommendation(data.latest.eco2));
|
updateCard("eco2Card", data.latest.eco2, getECO2Recommendation(data.latest.eco2));
|
||||||
updateCard("tvocCard", data.latest.tvoc, getTVOCRecommendation(data.latest.tvoc));
|
updateCard("tvocCard", data.latest.tvoc, getTVOCRecommendation(data.latest.tvoc));
|
||||||
updateCard("temperatureCard", data.latest.temperature, getTemperatureClass(data.latest.temperature));
|
updateCard("temperatureCard", data.latest.temperature, getTemperatureClass(data.latest.temperature));
|
||||||
updateCard("humidityCard", data.latest.humidity, getHumidityClass(data.latest.humidity));
|
updateCard("humidityCard", data.latest.humidity, getHumidityClass(data.latest.humidity));
|
||||||
|
|
||||||
|
// Update chart
|
||||||
chart.updateSeries([
|
chart.updateSeries([
|
||||||
{ name: 'TVOC (ppb)', data: data.history.tvoc },
|
{ name: 'TVOC (ppb)', data: data.history.tvoc },
|
||||||
{ name: 'eCO2 (ppm)', data: data.history.eco2 },
|
{ name: 'eCO2 (ppm)', data: data.history.eco2 },
|
||||||
{ name: 'Temperature (°C)', data: data.history.temperature },
|
{ name: 'Temperature (°C)', data: data.history.temperature },
|
||||||
{ name: 'Humidity (%)', data: data.history.humidity }
|
{ name: 'Humidity (%)', data: data.history.humidity }
|
||||||
]);
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCard(cardId, value, recommendation) {
|
||||||
|
const card = document.getElementById(cardId);
|
||||||
|
if (!card) {
|
||||||
|
console.error(`Card with ID ${cardId} not found.`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCard(cardId, value, recommendation) {
|
const valueText = card.querySelector(".card-body h4");
|
||||||
const card = document.getElementById(cardId);
|
const recText = card.querySelector(".card-body p");
|
||||||
const valueText = card.querySelector(".card-body h4");
|
|
||||||
const recText = card.querySelector(".card-body p");
|
if (valueText) {
|
||||||
card.className = `card text-center ${recommendation.class}`;
|
|
||||||
valueText.textContent = value.toFixed(1);
|
valueText.textContent = value.toFixed(1);
|
||||||
recText.textContent = recommendation.text;
|
} else {
|
||||||
|
console.error(`Value text element not found in ${cardId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAQIRecommendation(aqi) {
|
if (recText && recommendation) {
|
||||||
if (aqi === 1) return { class: "excellent", text: "Suitable for long-term living." };
|
recText.textContent = recommendation.text || "";
|
||||||
if (aqi === 2) return { class: "good", text: "Maintain adequate ventilation." };
|
card.className = `card text-center ${recommendation.class || ""}`;
|
||||||
if (aqi === 3) return { class: "moderate", text: "Strengthen ventilation." };
|
} else if (recText) {
|
||||||
if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." };
|
recText.textContent = "";
|
||||||
return { class: "unhealthy", text: "Avoid staying long; ventilate." };
|
card.className = "card text-center";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getECO2Recommendation(eco2) {
|
function getAQIRecommendation(aqi) {
|
||||||
if (eco2 > 1500) return { class: "unhealthy", text: "Serious pollution, ventilate!" };
|
if (aqi === 1) return { class: "excellent", text: "Suitable for long-term living." };
|
||||||
if (eco2 > 1000) return { class: "poor", text: "Polluted air, ventilation recommended." };
|
if (aqi === 2) return { class: "good", text: "Maintain adequate ventilation." };
|
||||||
if (eco2 > 800) return { class: "moderate", text: "Consider ventilation." };
|
if (aqi === 3) return { class: "moderate", text: "Strengthen ventilation." };
|
||||||
if (eco2 > 600) return { class: "good", text: "Air quality is fine." };
|
if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." };
|
||||||
return { class: "excellent", text: "Air quality is excellent." };
|
return { class: "unhealthy", text: "Avoid staying long; ventilate." };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTVOCRecommendation(tvoc) {
|
function getECO2Recommendation(eco2) {
|
||||||
if (tvoc > 6000) return { class: "unhealthy", text: "Headaches and nerve issues possible." };
|
if (eco2 > 1500) return { class: "unhealthy", text: "Serious pollution, ventilate!" };
|
||||||
if (tvoc > 750) return { class: "poor", text: "May cause headaches, ventilate." };
|
if (eco2 > 1000) return { class: "poor", text: "Polluted air, ventilation recommended." };
|
||||||
if (tvoc > 50) return { class: "moderate", text: "Some discomfort possible." };
|
if (eco2 > 800) return { class: "moderate", text: "Consider ventilation." };
|
||||||
return { class: "excellent", text: "No effects on health." };
|
if (eco2 > 600) return { class: "good", text: "Air quality is fine." };
|
||||||
}
|
return { class: "excellent", text: "Air quality is excellent." };
|
||||||
|
}
|
||||||
|
|
||||||
function getTemperatureClass(temp) {
|
function getTVOCRecommendation(tvoc) {
|
||||||
if (temp < 18) return { class: "moderate", text: "Too cold, consider heating." };
|
if (tvoc > 6000) return { class: "unhealthy", text: "Headaches and nerve issues possible." };
|
||||||
if (temp > 26) return { class: "poor", text: "Too hot, ventilate or cool down." };
|
if (tvoc > 750) return { class: "poor", text: "May cause headaches, ventilate." };
|
||||||
return { class: "good", text: "Comfortable temperature." };
|
if (tvoc > 50) return { class: "moderate", text: "Some discomfort possible." };
|
||||||
}
|
return { class: "excellent", text: "No effects on health." };
|
||||||
|
}
|
||||||
|
|
||||||
function getHumidityClass(hum) {
|
function getTemperatureClass(temp) {
|
||||||
if (hum < 30) return { class: "moderate", text: "Too dry, consider humidifying." };
|
if (temp < 18) return { class: "moderate"};
|
||||||
if (hum > 60) return { class: "poor", text: "Too humid, consider dehumidifying." };
|
if (temp > 26) return { class: "poor"};
|
||||||
return { class: "good", text: "Comfortable humidity." };
|
return { class: "good"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHumidityClass(hum) {
|
||||||
|
if (hum < 30) return { class: "moderate"};
|
||||||
|
if (hum > 60) return { class: "poor"};
|
||||||
|
return { class: "good"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data every second
|
||||||
|
setInterval(fetchData, 1000);
|
||||||
|
</script>
|
||||||
|
|
||||||
setInterval(fetchData, 1000);
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)rawliteral";
|
)rawliteral";
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ void setup() {
|
|||||||
|
|
||||||
// Sync time using NTP
|
// Sync time using NTP
|
||||||
syncTime();
|
syncTime();
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
// Initialize AHTX0 sensor
|
// Initialize AHTX0 sensor
|
||||||
if (!aht.begin()) {
|
if (!aht.begin()) {
|
||||||
@@ -150,11 +151,11 @@ void setup() {
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
.card.poor {
|
.card.poor {
|
||||||
background-color: #ff5722;
|
background-color:rgb(255, 60, 34);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.card.unhealthy {
|
.card.unhealthy {
|
||||||
background-color: #f44336;
|
background-color:rgb(244, 54, 244);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.apexcharts-tooltip {
|
.apexcharts-tooltip {
|
||||||
@@ -225,94 +226,121 @@ void setup() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
let chartOptions = {
|
let chartOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
height: 350,
|
height: 350,
|
||||||
animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } },
|
animations: { enabled: true, easing: 'linear', dynamicAnimation: { speed: 1000 } },
|
||||||
background: '#1e1e1e'
|
background: '#1e1e1e'
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{ name: 'TVOC (ppb)', data: [] },
|
{ name: 'TVOC (ppb)', data: [] },
|
||||||
{ name: 'eCO2 (ppm)', data: [] },
|
{ name: 'eCO2 (ppm)', data: [] },
|
||||||
{ name: 'Temperature (°C)', data: [] },
|
{ name: 'Temperature (°C)', data: [] },
|
||||||
{ name: 'Humidity (%)', data: [] }
|
{ name: 'Humidity (%)', data: [] }
|
||||||
],
|
],
|
||||||
xaxis: { type: 'datetime', labels: { style: { colors: '#ffffff' } } },
|
xaxis: { type: 'datetime', labels: { style: { colors: '#ffffff' } } },
|
||||||
yaxis: { labels: { style: { colors: '#ffffff' } } },
|
yaxis: { labels: { style: { colors: '#ffffff' } } },
|
||||||
tooltip: { theme: 'dark' },
|
tooltip: { theme: 'dark' }
|
||||||
};
|
};
|
||||||
|
|
||||||
let chart = new ApexCharts(document.querySelector("#chart"), chartOptions);
|
let chart = new ApexCharts(document.querySelector("#chart"), chartOptions);
|
||||||
chart.render();
|
chart.render();
|
||||||
|
|
||||||
async function fetchData() {
|
async function fetchData() {
|
||||||
|
try {
|
||||||
const response = await fetch('/data');
|
const response = await fetch('/data');
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to fetch data: ${response.statusText}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
console.log(data.latest.humidity);
|
// Update cards
|
||||||
|
|
||||||
updateCard("aqiCard", data.latest.aqi, getAQIRecommendation(data.latest.aqi));
|
updateCard("aqiCard", data.latest.aqi, getAQIRecommendation(data.latest.aqi));
|
||||||
updateCard("eco2Card", data.latest.eco2, getECO2Recommendation(data.latest.eco2));
|
updateCard("eco2Card", data.latest.eco2, getECO2Recommendation(data.latest.eco2));
|
||||||
updateCard("tvocCard", data.latest.tvoc, getTVOCRecommendation(data.latest.tvoc));
|
updateCard("tvocCard", data.latest.tvoc, getTVOCRecommendation(data.latest.tvoc));
|
||||||
updateCard("temperatureCard", data.latest.temperature, getTemperatureClass(data.latest.temperature));
|
updateCard("temperatureCard", data.latest.temperature, getTemperatureClass(data.latest.temperature));
|
||||||
updateCard("humidityCard", data.latest.humidity, getHumidityClass(data.latest.humidity));
|
updateCard("humidityCard", data.latest.humidity, getHumidityClass(data.latest.humidity));
|
||||||
|
|
||||||
|
// Update chart
|
||||||
chart.updateSeries([
|
chart.updateSeries([
|
||||||
{ name: 'TVOC (ppb)', data: data.history.tvoc },
|
{ name: 'TVOC (ppb)', data: data.history.tvoc },
|
||||||
{ name: 'eCO2 (ppm)', data: data.history.eco2 },
|
{ name: 'eCO2 (ppm)', data: data.history.eco2 },
|
||||||
{ name: 'Temperature (°C)', data: data.history.temperature },
|
{ name: 'Temperature (°C)', data: data.history.temperature },
|
||||||
{ name: 'Humidity (%)', data: data.history.humidity }
|
{ name: 'Humidity (%)', data: data.history.humidity }
|
||||||
]);
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching data:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCard(cardId, value, recommendation) {
|
||||||
|
const card = document.getElementById(cardId);
|
||||||
|
if (!card) {
|
||||||
|
console.error(`Card with ID ${cardId} not found.`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCard(cardId, value, recommendation) {
|
const valueText = card.querySelector(".card-body h4");
|
||||||
const card = document.getElementById(cardId);
|
const recText = card.querySelector(".card-body p");
|
||||||
const valueText = card.querySelector(".card-body h4");
|
|
||||||
const recText = card.querySelector(".card-body p");
|
if (valueText) {
|
||||||
card.className = `card text-center ${recommendation.class}`;
|
|
||||||
valueText.textContent = value.toFixed(1);
|
valueText.textContent = value.toFixed(1);
|
||||||
recText.textContent = recommendation.text;
|
} else {
|
||||||
|
console.error(`Value text element not found in ${cardId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAQIRecommendation(aqi) {
|
if (recText && recommendation) {
|
||||||
if (aqi === 1) return { class: "excellent", text: "Suitable for long-term living." };
|
recText.textContent = recommendation.text || "";
|
||||||
if (aqi === 2) return { class: "good", text: "Maintain adequate ventilation." };
|
card.className = `card text-center ${recommendation.class || ""}`;
|
||||||
if (aqi === 3) return { class: "moderate", text: "Strengthen ventilation." };
|
} else if (recText) {
|
||||||
if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." };
|
recText.textContent = "";
|
||||||
return { class: "unhealthy", text: "Avoid staying long; ventilate." };
|
card.className = "card text-center";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getECO2Recommendation(eco2) {
|
function getAQIRecommendation(aqi) {
|
||||||
if (eco2 > 1500) return { class: "unhealthy", text: "Serious pollution, ventilate!" };
|
if (aqi === 1) return { class: "excellent", text: "Suitable for long-term living." };
|
||||||
if (eco2 > 1000) return { class: "poor", text: "Polluted air, ventilation recommended." };
|
if (aqi === 2) return { class: "good", text: "Maintain adequate ventilation." };
|
||||||
if (eco2 > 800) return { class: "moderate", text: "Consider ventilation." };
|
if (aqi === 3) return { class: "moderate", text: "Strengthen ventilation." };
|
||||||
if (eco2 > 600) return { class: "good", text: "Air quality is fine." };
|
if (aqi === 4) return { class: "poor", text: "Find pollution sources, ventilate more." };
|
||||||
return { class: "excellent", text: "Air quality is excellent." };
|
return { class: "unhealthy", text: "Avoid staying long; ventilate." };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTVOCRecommendation(tvoc) {
|
function getECO2Recommendation(eco2) {
|
||||||
if (tvoc > 6000) return { class: "unhealthy", text: "Headaches and nerve issues possible." };
|
if (eco2 > 1500) return { class: "unhealthy", text: "Serious pollution, ventilate!" };
|
||||||
if (tvoc > 750) return { class: "poor", text: "May cause headaches, ventilate." };
|
if (eco2 > 1000) return { class: "poor", text: "Polluted air, ventilation recommended." };
|
||||||
if (tvoc > 50) return { class: "moderate", text: "Some discomfort possible." };
|
if (eco2 > 800) return { class: "moderate", text: "Consider ventilation." };
|
||||||
return { class: "excellent", text: "No effects on health." };
|
if (eco2 > 600) return { class: "good", text: "Air quality is fine." };
|
||||||
}
|
return { class: "excellent", text: "Air quality is excellent." };
|
||||||
|
}
|
||||||
|
|
||||||
function getTemperatureClass(temp) {
|
function getTVOCRecommendation(tvoc) {
|
||||||
if (temp < 18) return { class: "moderate", text: "Too cold, consider heating." };
|
if (tvoc > 6000) return { class: "unhealthy", text: "Headaches and nerve issues possible." };
|
||||||
if (temp > 26) return { class: "poor", text: "Too hot, ventilate or cool down." };
|
if (tvoc > 750) return { class: "poor", text: "May cause headaches, ventilate." };
|
||||||
return { class: "good", text: "Comfortable temperature." };
|
if (tvoc > 50) return { class: "moderate", text: "Some discomfort possible." };
|
||||||
}
|
return { class: "excellent", text: "No effects on health." };
|
||||||
|
}
|
||||||
|
|
||||||
function getHumidityClass(hum) {
|
function getTemperatureClass(temp) {
|
||||||
if (hum < 30) return { class: "moderate", text: "Too dry, consider humidifying." };
|
if (temp < 18) return { class: "moderate"};
|
||||||
if (hum > 60) return { class: "poor", text: "Too humid, consider dehumidifying." };
|
if (temp > 26) return { class: "poor"};
|
||||||
return { class: "good", text: "Comfortable humidity." };
|
return { class: "good"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHumidityClass(hum) {
|
||||||
|
if (hum < 30) return { class: "moderate"};
|
||||||
|
if (hum > 60) return { class: "poor"};
|
||||||
|
return { class: "good"};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data every second
|
||||||
|
setInterval(fetchData, 1000);
|
||||||
|
</script>
|
||||||
|
|
||||||
setInterval(fetchData, 1000);
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)rawliteral";
|
)rawliteral";
|
||||||
|
|||||||
Reference in New Issue
Block a user