// Minimaler Empfangs-Server: ESP8266 ➔ POST /api/messwerte ➔ Frontend (GET /api/messwerte) // Nur Node-Bordmittel, kein npm install nötig. Start: node backend/server.js [Port] const http = require("http"); const fs = require("fs"); const path = require("path"); const PORT = Number(process.argv[2] || process.env.PORT || 3000); const FRONTEND_DIR = path.join(__dirname, "..", "frontend"); const MAX_VERLAUF = 100; const MAX_BODY_BYTES = 1024; // Erlaubte Felder des Datenvertrags (siehe backend/README.md) const FELDER = ["temperatur", "luftfeuchte", "eco2", "tvoc"]; const verlauf = []; // { zeit: ISO-String, temperatur, luftfeuchte, eco2, tvoc } const MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".svg": "image/svg+xml", }; function sendJson(res, status, obj) { res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify(obj)); } function leseBody(req, callback) { let body = ""; let zuGross = false; req.on("data", (chunk) => { body += chunk; if (body.length > MAX_BODY_BYTES) { zuGross = true; req.destroy(); } }); req.on("end", () => callback(zuGross ? null : body)); } function messwertAnnehmen(req, res) { leseBody(req, (body) => { if (body === null) return sendJson(res, 413, { fehler: "Anfrage zu gross" }); let daten; try { daten = JSON.parse(body); } catch { return sendJson(res, 400, { fehler: "Ungueltiges JSON" }); } const eintrag = { zeit: new Date().toISOString() }; for (const feld of FELDER) { // Fehlende Sensoren (z. B. CCS811 noch nicht bereit) dürfen fehlen/null sein const wert = daten[feld]; if (wert === undefined || wert === null) { eintrag[feld] = null; } else if (typeof wert === "number" && Number.isFinite(wert)) { eintrag[feld] = wert; } else { return sendJson(res, 400, { fehler: `Feld '${feld}' muss eine Zahl sein` }); } } verlauf.push(eintrag); if (verlauf.length > MAX_VERLAUF) verlauf.shift(); console.log(new Date().toLocaleTimeString("de-DE"), JSON.stringify(eintrag)); sendJson(res, 200, { ok: true }); }); } function statischAusliefern(req, res) { const pfad = new URL(req.url, "http://x").pathname; const datei = path.normalize(path.join(FRONTEND_DIR, pfad === "/" ? "index.html" : pfad)); if (!datei.startsWith(FRONTEND_DIR + path.sep)) return sendJson(res, 403, { fehler: "Verboten" }); fs.readFile(datei, (err, inhalt) => { if (err) return sendJson(res, 404, { fehler: "Nicht gefunden" }); res.writeHead(200, { "Content-Type": MIME[path.extname(datei)] || "application/octet-stream" }); res.end(inhalt); }); } const server = http.createServer((req, res) => { const pfad = new URL(req.url, "http://x").pathname; if (pfad === "/api/messwerte") { if (req.method === "POST") return messwertAnnehmen(req, res); if (req.method === "GET") { return sendJson(res, 200, { aktuell: verlauf[verlauf.length - 1] || null, verlauf }); } return sendJson(res, 405, { fehler: "Methode nicht erlaubt" }); } if (req.method === "GET") return statischAusliefern(req, res); sendJson(res, 405, { fehler: "Methode nicht erlaubt" }); }); // 0.0.0.0, damit der ESP8266 im WLAN den Server erreicht server.listen(PORT, "0.0.0.0", () => { console.log(`Server laeuft auf http://0.0.0.0:${PORT} (Frontend: http://localhost:${PORT})`); });