feat: local-cpl-sim.mjs analogInputs /Messwerteingäge / analoge Eingänge

This commit is contained in:
ISA
2025-09-04 13:36:13 +02:00
parent 9c7ad37233
commit 3daa6b1dbb
6 changed files with 180 additions and 9 deletions

View File

@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer # 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) NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)

View File

@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.838 NEXT_PUBLIC_APP_VERSION=1.6.839
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,8 @@
## [1.6.839] 2025-09-04
- feat: local-cpl-sim.mjs digitalInputs /Messwerteingänge
---
## [1.6.838] 2025-09-04 ## [1.6.838] 2025-09-04
- feat: local-cpl-sim system - feat: local-cpl-sim system

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.838", "version": "1.6.839",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.838", "version": "1.6.839",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@headlessui/react": "^2.2.4", "@headlessui/react": "^2.2.4",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.838", "version": "1.6.839",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 3000", "dev": "next dev -p 3000",

View File

@@ -98,7 +98,10 @@ function tryServeSpecialMocks(res, relPath) {
const rp = relPath.replace(/^\/+/, ""); const rp = relPath.replace(/^\/+/, "");
// Serve ready JS/JSON mocks for some routes // Serve ready JS/JSON mocks for some routes
// Digital Inputs JSON // Digital Inputs JSON
if (rp === "CPL/SERVICE/digitalInputs.json") { if (
rp === "CPL/SERVICE/digitalInputs.json" ||
rp === "CPL/Service/digitalInputs.json"
) {
const mockPath = path.join( const mockPath = path.join(
process.cwd(), process.cwd(),
"mocks", "mocks",
@@ -108,8 +111,11 @@ function tryServeSpecialMocks(res, relPath) {
); );
if (exists(mockPath)) return streamRaw(res, mockPath); if (exists(mockPath)) return streamRaw(res, mockPath);
} }
// Analog Inputs JSON // Analog Inputs JSON (case-insensitive SERVICE)
if (rp === "CPL/SERVICE/analogInputs.json") { if (
rp === "CPL/SERVICE/analogInputs.json" ||
rp === "CPL/Service/analogInputs.json"
) {
const mockPath = path.join( const mockPath = path.join(
process.cwd(), process.cwd(),
"mocks", "mocks",
@@ -520,7 +526,7 @@ const server = http.createServer(async (req, res) => {
res.end(JSON.stringify(filtered)); res.end(JSON.stringify(filtered));
} catch { } catch {
res.writeHead(500, { "Content-Type": "application/json" }); res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Interner Serverfehler" })); res.end(JSON.stringify({ error: "Internal Server Error" }));
} }
return; return;
} }
@@ -541,6 +547,29 @@ const server = http.createServer(async (req, res) => {
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
}); });
res.end(raw); 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 { } catch {
res.writeHead(500, { "Content-Type": "application/json" }); res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Interner Serverfehler" })); res.end(JSON.stringify({ error: "Interner Serverfehler" }));
@@ -548,6 +577,101 @@ const server = http.createServer(async (req, res) => {
return; 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 // Dev API: digital inputs updater used by UI Save button in dev/exported mode
if (pathname === "/api/cpl/updateDigitalInputs") { if (pathname === "/api/cpl/updateDigitalInputs") {
if (req.method !== "POST") { if (req.method !== "POST") {
@@ -680,6 +804,48 @@ const server = http.createServer(async (req, res) => {
// fall-through // 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 // Other non-file commands: just 200 OK
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
res.end("OK"); res.end("OK");