This commit is contained in:
Hickmeister
2025-01-20 23:31:58 +00:00
parent 70395d31a6
commit ac0d4dd2e2
2 changed files with 180 additions and 124 deletions

View File

@@ -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";

View File

@@ -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";