GruppeC/backend/server.js

98 lines
3.5 KiB
JavaScript

// 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})`);
});