Files
homeassistant-config/esphome/components/air_quality_web/page.h
T

83 lines
4.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
namespace esphome::air_quality_web {
static constexpr char PAGE[] = R"AQPAGE(<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>Luftqualität im Büro</title>
<style>
:root{font-family:system-ui,sans-serif;color-scheme:light dark;background:#f1f5f9;color:#17212f}
*{box-sizing:border-box}body{margin:0;padding:clamp(1rem,5vw,3rem)}main{max-width:58rem;margin:auto}
h1{font-size:clamp(1.7rem,5vw,2.6rem);margin-bottom:.5rem}.intro,footer{color:#526173}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:1rem;margin:2rem 0}
article{background:#fff;border:2px solid #b4c2d1;border-radius:1rem;padding:1.5rem;min-width:0}
h2{font-size:1.1rem;margin:0 0 1rem}.value{font-size:clamp(2.2rem,6vw,3.6rem);font-weight:700;font-variant-numeric:tabular-nums}
.unit{font-size:1.1rem;font-weight:400}.hint{min-height:1.5em;margin-bottom:0}
article.good{border-color:#15803d}article.orange{border-color:#c05a00;background:#fff4df}article.red{border-color:#c52b2b;background:#ffeded}
#status{font-weight:600}footer{font-size:.9rem;line-height:1.6}
@media(prefers-color-scheme:dark){:root{background:#101923;color:#eef3fa}article{background:#1b2837}.intro,footer{color:#b1c0d1}article.orange{background:#402a13;border-color:#ffae4a}article.red{background:#401b21;border-color:#ff7979}}
</style>
</head>
<body><main>
<h1>Luftqualität im Büro</h1>
<p class="intro">Aktuelle Messwerte · Aktualisierung alle 5 Sekunden</p>
<p id="status" role="status" aria-live="polite">Verbindung wird hergestellt …</p>
<div class="grid">
<article id="co2-card"><h2>CO₂</h2><div class="value"><span id="co2">—</span> <span class="unit">ppm</span></div><p class="hint" id="co2-hint">Keine Messdaten</p></article>
<article><h2>Temperatur</h2><div class="value"><span id="temperature">—</span> <span class="unit">°C</span></div><p class="hint" id="temperature-hint">Keine Messdaten</p></article>
<article><h2>Feuchtigkeit</h2><div class="value"><span id="humidity">—</span> <span class="unit">%</span></div><p class="hint" id="humidity-hint">Keine Messdaten</p></article>
</div>
<footer>CO₂: Orange bei mehr als 1200 ppm, Rot bei mehr als 1600 ppm.<br>Nur aktuelle Luftmesswerte – keine Aufzeichnung.</footer>
<noscript>Bitte JavaScript aktivieren, um die aktuellen Messwerte anzuzeigen.</noscript>
</main>
<script>
'use strict';
const keys = ['co2', 'temperature', 'humidity'];
const status = document.getElementById('status');
const card = document.getElementById('co2-card');
const formats = [0, 1, 1].map(d => new Intl.NumberFormat('de-DE', {minimumFractionDigits:d, maximumFractionDigits:d}));
function render(data) {
let available = 0;
keys.forEach((key, i) => {
const value = data[key];
const valid = typeof value === 'number' && Number.isFinite(value);
document.getElementById(key).textContent = valid ? formats[i].format(value) : '—';
document.getElementById(key + '-hint').textContent = valid ? 'Aktueller Messwert' : 'Keine Messdaten';
if (valid) available++;
});
const co2 = data.co2;
const valid = typeof co2 === 'number' && Number.isFinite(co2);
card.className = valid ? (co2 > 1600 ? 'red' : co2 > 1200 ? 'orange' : 'good') : '';
if (valid) document.getElementById('co2-hint').textContent = co2 > 1600 ? 'Bitte lüften – CO₂ sehr hoch' : co2 > 1200 ? 'Lüften empfohlen – CO₂ erhöht' : 'CO₂ im grünen Bereich';
return available;
}
async function poll() {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 4000);
try {
const response = await fetch('/api/air-quality', {cache:'no-store', credentials:'omit', redirect:'error', signal:controller.signal});
if (!response.ok) throw new Error('HTTP');
const data = await response.json();
if (!data || Object.keys(data).length !== 3 || !keys.every(k => Object.hasOwn(data,k) && (data[k] === null || (typeof data[k] === 'number' && Number.isFinite(data[k]))))) throw new Error('Datenformat');
const count = render(data);
status.textContent = count === 3 ? 'Online · Messwerte aktuell' : count === 0 ? 'Online · Noch keine Messdaten' : 'Online · Einzelne Messwerte fehlen';
} catch (_) {
render({co2:null, temperature:null, humidity:null});
status.textContent = 'Offline · Keine Verbindung – erneuter Versuch in 5 Sekunden';
} finally {
clearTimeout(timeout);
// Schedule only after fetch and JSON parsing finish: never overlap requests.
setTimeout(poll, 5000);
}
}
poll();
</script>
</body>
</html>)AQPAGE";
} // namespace esphome::air_quality_web