diff --git a/.env.development b/.env.development index 3fb4e52..e007cf8 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.838 +NEXT_PUBLIC_APP_VERSION=1.6.839 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 101de0e..32345f4 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.838 +NEXT_PUBLIC_APP_VERSION=1.6.839 NEXT_PUBLIC_CPL_MODE=production \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 205e53c..5ab8e9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [1.6.839] – 2025-09-04 + +- feat: local-cpl-sim.mjs digitalInputs /Messwerteingänge + +--- ## [1.6.838] – 2025-09-04 - feat: local-cpl-sim system diff --git a/package-lock.json b/package-lock.json index 8506e7c..003f0ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cpl-v4", - "version": "1.6.838", + "version": "1.6.839", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cpl-v4", - "version": "1.6.838", + "version": "1.6.839", "dependencies": { "@fontsource/roboto": "^5.1.0", "@headlessui/react": "^2.2.4", diff --git a/package.json b/package.json index 33df675..6f2a60c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cpl-v4", - "version": "1.6.838", + "version": "1.6.839", "private": true, "scripts": { "dev": "next dev -p 3000", diff --git a/scripts/local-cpl-sim.mjs b/scripts/local-cpl-sim.mjs index ccbdea4..ece79e5 100644 --- a/scripts/local-cpl-sim.mjs +++ b/scripts/local-cpl-sim.mjs @@ -98,7 +98,10 @@ function tryServeSpecialMocks(res, relPath) { const rp = relPath.replace(/^\/+/, ""); // Serve ready JS/JSON mocks for some routes // Digital Inputs JSON - if (rp === "CPL/SERVICE/digitalInputs.json") { + if ( + rp === "CPL/SERVICE/digitalInputs.json" || + rp === "CPL/Service/digitalInputs.json" + ) { const mockPath = path.join( process.cwd(), "mocks", @@ -108,8 +111,11 @@ function tryServeSpecialMocks(res, relPath) { ); if (exists(mockPath)) return streamRaw(res, mockPath); } - // Analog Inputs JSON - if (rp === "CPL/SERVICE/analogInputs.json") { + // Analog Inputs JSON (case-insensitive SERVICE) + if ( + rp === "CPL/SERVICE/analogInputs.json" || + rp === "CPL/Service/analogInputs.json" + ) { const mockPath = path.join( process.cwd(), "mocks", @@ -520,7 +526,7 @@ const server = http.createServer(async (req, res) => { res.end(JSON.stringify(filtered)); } catch { res.writeHead(500, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Interner Serverfehler" })); + res.end(JSON.stringify({ error: "Internal Server Error" })); } return; } @@ -541,6 +547,29 @@ const server = http.createServer(async (req, res) => { "Cache-Control": "no-cache", }); res.end(raw); + } catch { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Internal Server Error" })); + } + return; + } + + // Dev API: analog inputs getter used by exported app on localhost + if (pathname === "/api/cpl/getAnalogInputsHandler") { + try { + const p = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "SERVICE", + "analogInputsMockData.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" })); @@ -548,6 +577,101 @@ const server = http.createServer(async (req, res) => { return; } + // Dev API: analog inputs history for charts + if (pathname === "/api/cpl/getAnalogInputsHistory") { + try { + const zeitraum = url.searchParams.get("zeitraum"); // DIA0|DIA1|DIA2 + const eingangStr = url.searchParams.get("eingang"); // 1..8 + const validZ = ["DIA0", "DIA1", "DIA2"]; + const eingang = Number(eingangStr); + if ( + !validZ.includes(zeitraum) || + !Number.isInteger(eingang) || + eingang < 1 || + eingang > 8 + ) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end( + JSON.stringify({ + error: "Ungültige Parameter", + erwartet: { zeitraum: "DIA0|DIA1|DIA2", eingang: "1..8" }, + }) + ); + return; + } + const fp = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "chartsData", + "analogInputs", + String(eingang), + `${zeitraum}.json` + ); + if (!exists(fp)) { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Keine Daten gefunden" })); + return; + } + const raw = fs.readFileSync(fp, "utf8"); + const daten = JSON.parse(raw); + res.writeHead(200, { + "Content-Type": "application/json", + "Cache-Control": "no-cache", + }); + res.end(JSON.stringify({ eingang, zeitraum, daten })); + } catch { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Internal Server Error" })); + } + return; + } + + // Dev API: update analog inputs settings (labels/offset/factor/unit/loggerInterval) + if (pathname === "/api/cpl/updateAnalogInputsSettingsHandler") { + 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 updates = Array.isArray(body?.updates) ? body.updates : null; + if (!updates) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Missing updates array" })); + return; + } + const fp = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "SERVICE", + "analogInputsMockData.json" + ); + const json = JSON.parse(fs.readFileSync(fp, "utf8")); + for (const u of updates) { + const { key, index, value } = u || {}; + if ( + typeof key === "string" && + Number.isInteger(index) && + index >= 0 + ) { + if (Array.isArray(json[key])) { + json[key][index] = value; + } + } + } + fs.writeFileSync(fp, JSON.stringify(json, null, 2), "utf8"); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ success: true })); + } catch { + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Update fehlgeschlagen" })); + } + return; + } + // Dev API: digital inputs updater used by UI Save button in dev/exported mode if (pathname === "/api/cpl/updateDigitalInputs") { if (req.method !== "POST") { @@ -680,6 +804,48 @@ const server = http.createServer(async (req, res) => { // fall-through } } + // Service commands: analog inputs history via DIA0/DIA1/DIA2 + // Example: seite.ACP&DIA1=YYYY;MM;DD;YYYY;MM;DD;1xx;1 where 1xx is 100 + (eingang-1) + if (/^seite\.ACP/i.test(q) && /DIA[0-2]=/i.test(q)) { + try { + const m = q.match(/(DIA[0-2])=([^&]+)/i); + if (m) { + const zeitraum = m[1].toUpperCase(); + const parts = m[2].split(";"); + // parts: [fy,fm,fd,ty,tm,td,channelCode(1xx), ...] + const channel = parts.length >= 7 ? Number(parts[6]) : NaN; + const eingang = Number.isFinite(channel) ? channel - 99 : NaN; + if ( + ["DIA0", "DIA1", "DIA2"].includes(zeitraum) && + Number.isInteger(eingang) && + eingang >= 1 && + eingang <= 8 + ) { + const fp = path.join( + process.cwd(), + "mocks", + "device-cgi-simulator", + "chartsData", + "analogInputs", + String(eingang), + `${zeitraum}.json` + ); + if (exists(fp)) { + const raw = fs.readFileSync(fp, "utf8"); + const daten = JSON.parse(raw); + res.writeHead(200, { + "Content-Type": "application/json", + "Cache-Control": "no-cache", + }); + res.end(JSON.stringify(daten)); + return; + } + } + } + } catch { + // ignore malformed DIA query + } + } // Other non-file commands: just 200 OK res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end("OK");