From 9c7ad372334039f0a4737f0cc53e594b16ae998b Mon Sep 17 00:00:00 2001 From: ISA Date: Thu, 4 Sep 2025 13:24:10 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20local-cpl-sim.mjs=20digitalInputs=20/Me?= =?UTF-8?q?sswerteing=C3=A4nge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .env.production | 2 +- CHANGELOG.md | 5 ++ package-lock.json | 4 +- package.json | 2 +- scripts/local-cpl-sim.mjs | 126 +++++++++++++++++++++++++++++++++++++- 6 files changed, 135 insertions(+), 6 deletions(-) diff --git a/.env.development b/.env.development index b617f7b..3fb4e52 100644 --- a/.env.development +++ b/.env.development @@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_USE_CGI=false # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.837 +NEXT_PUBLIC_APP_VERSION=1.6.838 NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) diff --git a/.env.production b/.env.production index 1183bea..101de0e 100644 --- a/.env.production +++ b/.env.production @@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_USE_CGI=true # App-Versionsnummer -NEXT_PUBLIC_APP_VERSION=1.6.837 +NEXT_PUBLIC_APP_VERSION=1.6.838 NEXT_PUBLIC_CPL_MODE=production \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ff7b84a..205e53c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.6.838] – 2025-09-04 + +- feat: local-cpl-sim system + +--- ## [1.6.837] – 2025-09-04 - feat: local-cpl-sim meldungen/Berichte diff --git a/package-lock.json b/package-lock.json index bbae542..8506e7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cpl-v4", - "version": "1.6.837", + "version": "1.6.838", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cpl-v4", - "version": "1.6.837", + "version": "1.6.838", "dependencies": { "@fontsource/roboto": "^5.1.0", "@headlessui/react": "^2.2.4", diff --git a/package.json b/package.json index 1f238ee..33df675 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cpl-v4", - "version": "1.6.837", + "version": "1.6.838", "private": true, "scripts": { "dev": "next dev -p 3000", diff --git a/scripts/local-cpl-sim.mjs b/scripts/local-cpl-sim.mjs index 5b8a620..ccbdea4 100644 --- a/scripts/local-cpl-sim.mjs +++ b/scripts/local-cpl-sim.mjs @@ -14,6 +14,30 @@ const ROOT = path.join(process.cwd(), "out"); const textCache = new Map(); // key: absolute path, value: { mtimeMs, body, contentType } let messagesAllCache = null; // cache parsed meldungen/messages_all.json +// Helper to read JSON body from POST requests +function readRequestBody(req) { + return new Promise((resolve, reject) => { + let data = ""; + req.on("data", (chunk) => { + data += chunk; + // Basic guard against huge bodies in local dev + if (data.length > 1_000_000) { + req.destroy(); + reject(new Error("Request body too large")); + } + }); + req.on("end", () => { + try { + const json = data ? JSON.parse(data) : {}; + resolve(json); + } catch { + resolve({}); + } + }); + req.on("error", reject); + }); +} + function exists(p) { try { fs.accessSync(p, fs.constants.F_OK); @@ -453,7 +477,7 @@ function notFound(res) { res.end("Not Found"); } -const server = http.createServer((req, res) => { +const server = http.createServer(async (req, res) => { try { const url = new URL(req.url, `http://localhost:${PORT}`); const pathname = decodeURIComponent(url.pathname); @@ -501,6 +525,106 @@ const server = http.createServer((req, res) => { return; } + // Dev API: digital inputs getter used by exported app on localhost + if (pathname === "/api/cpl/getDigitalInputsHandler") { + try { + const p = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "SERVICE", + "digitalInputsMockData.json" + ); + const raw = fs.readFileSync(p, "utf8"); + res.writeHead(200, { + "Content-Type": "application/json", + "Cache-Control": "no-cache", + }); + res.end(raw); + } catch { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Interner Serverfehler" })); + } + return; + } + + // Dev API: digital inputs updater used by UI Save button in dev/exported mode + if (pathname === "/api/cpl/updateDigitalInputs") { + if (req.method !== "POST") { + res.writeHead(405, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Only POST supported" })); + return; + } + try { + const body = await readRequestBody(req); + const { + id, + label, + invert, + timeFilter, + weighting, + zaehlerAktiv, + eingangOffline, + } = body || {}; + if (typeof id !== "number" || id < 1 || id > 32) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Ungültige ID (1–32 erlaubt)" })); + return; + } + const fp = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "SERVICE", + "digitalInputsMockData.json" + ); + const current = JSON.parse(fs.readFileSync(fp, "utf8")); + const idx = id - 1; + if (typeof label === "string" && Array.isArray(current.win_de_label)) + current.win_de_label[idx] = label; + if (typeof invert === "number" && Array.isArray(current.win_de_invert)) + current.win_de_invert[idx] = invert; + if ( + typeof timeFilter === "number" && + Array.isArray(current.win_de_time_filter) + ) + current.win_de_time_filter[idx] = timeFilter; + if ( + typeof weighting === "number" && + Array.isArray(current.win_de_weighting) + ) + current.win_de_weighting[idx] = weighting; + if ( + typeof zaehlerAktiv === "number" && + Array.isArray(current.win_de_counter_active) + ) + current.win_de_counter_active[idx] = zaehlerAktiv; + if ( + typeof eingangOffline === "number" && + Array.isArray(current.win_de_offline) + ) + current.win_de_offline[idx] = eingangOffline; + + fs.writeFileSync(fp, JSON.stringify(current, null, 2), "utf8"); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + message: `Update erfolgreich für ID ${id}`, + id, + label, + invert, + timeFilter, + weighting, + eingangOffline, + }) + ); + } catch { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Update fehlgeschlagen" })); + } + return; + } + // CPL? mapping: /CPL?/CPL/... -> /CPL/... and service commands if (pathname === "/CPL" && rawQuery) { const q = decodeURIComponent(rawQuery);