feat: Digitale Ausgänge vollständig implementiert (Lesen & Schreiben in allen Modi)

- Unterstützung für drei Modi implementiert: json, jsmock und production
- fetchDigitalOutputsService.ts erkennt NEXT_PUBLIC_CPL_MODE und lädt Daten je nach Umgebung
- API-Handler /api/cpl/updateDigitalOutputsHandler verarbeitet POST-Anfragen für json und jsmock
- In production wird Statusänderung per Redirect (window.location.href) an das CPL gesendet
- Redux-Slice für digitale Ausgänge vollständig angebunden
- UI (DigitalOutputsWidget.tsx) zeigt Status und ermöglicht das Umschalten
- Dokumentation als README_digitalOutputs_final.md mit UML-Diagrammen ergänzt
- CHANGELOG.md auf Version 1.6.417 aktualisiert
This commit is contained in:
ISA
2025-06-19 09:01:49 +02:00
parent 1b01f37f90
commit dbb38cc7d8
17 changed files with 401 additions and 64 deletions

View File

@@ -6,6 +6,5 @@ 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.417 NEXT_PUBLIC_APP_VERSION=1.6.418
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsmock (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsmock (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.417 NEXT_PUBLIC_APP_VERSION=1.6.418
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -4,6 +4,26 @@ Alle Änderungen und Versionen des CPLv4.0 Frontends chronologisch dokumentiert.
--- ---
## [1.6.417] 2025-06-19
### Feature: Digitale Ausgänge vollständig implementiert (Lesen & Schreiben in 3 Modi)
- Die Schaltausgänge (digitale Ausgänge) sind jetzt in allen Modi vollständig funktionsfähig:
- `json` (lokale Entwicklung mit editierbaren Mock-Daten)
- `jsmock` (Simulation durch JavaScript-Variablen im `window`-Objekt)
- `production` (Platzhalterersetzung über CGI durch das echte CPL-Gerät)
- Die API `/api/cpl/updateDigitalOutputsHandler` verarbeitet POST-Daten je nach Modus:
- In `json`: Speicherung in `digitalOutputsMockData.json`
- In `jsmock`: Live-Änderung in `digitalOutputsMockData.js` per Regex
- In `production`: Statusänderung über Redirect zu `/CPL?digitalOutputs.html&DAS0X=1`
- Die Datei `fetchDigitalOutputsService.ts` erkennt den aktiven Modus und lädt Daten kontextsensitiv
- Alle Werte werden über Redux bereitgestellt, die UI nutzt `useSelector()` zur Anzeige in `DigitalOutputsWidget.tsx`
- Mermaid-Dokumentation zur Architektur im Projekt ergänzt (`README_digitalOutputs_final.md`)
Alle Änderungen und Versionen des CPLv4.0 Frontends chronologisch dokumentiert.
---
## [1.6.407] 2025-06-16 ## [1.6.407] 2025-06-16
### Feature: Kabelname statt Bezeichnung ### Feature: Kabelname statt Bezeichnung

View File

@@ -0,0 +1,99 @@
# 📦 Datenverarbeitung in CPL: JSON, jsmock, production
Dieses Dokument beschreibt die Architektur zur Verarbeitung von Digitalausgängen (DA) im CPL-System unter verschiedenen Modi.
## ✅ Unterstützte Modi
- `json` Entwicklung mit lokalen Mock-JSON-Dateien (editierbar)
- `jsmock` Simulation des Geräts mit einer JS-Datei, die `window`-Variablen setzt
- `production` Echte CPL-Hardware mit CGI-Platzhaltern (JS in HTML/JS-Dateien)
---
## 🧩 Datenfluss-Überblick (UML Diagramm)
```mermaid
flowchart TD
A[NEXT_PUBLIC_CPL_MODE] --> B{Modus}
B -->|json| C[API: fetchDigitalOutputsAPIHandler]
C --> D[JSON-Datei (editable)]
B -->|jsmock| E[fetchDigitalOutputsService()]
E --> F[<script> digitalOutputsMockData.js]
F --> G[window.win_da_state / bezeichnung]
B -->|production| H[fetchDigitalOutputsService()]
H --> I[<script> /CPL/digitalOutputs.js]
I --> J[window.win_da_state / bezeichnung]
D & G & J --> K[Redux Store / Slice]
K --> L[Redux Selector]
L --> M[UI: DigitalOutputsWidget.tsx]
```
---
## 🧠 Entscheidungshilfe
| Modus | Quelle | Vorteil | Änderbar |
| ------------ | ------------------------------------------ | ---------------------------- | -------------- |
| `json` | `/mocks/api/SERVICE/*.json` | Schnell editierbar & testbar | ✅ Ja |
| `jsmock` | `/mocks/device-cgi-simulator/SERVICE/*.js` | Realistische Simulation | ❌ Nur per API |
| `production` | `/public/CPL/*.js` | Reale Gerätedaten | ❌ Nein |
---
## 🔁 Aktualisierung der Daten
### json
-`POST /api/cpl/updateDigitalOutputsHandler`
- 📂 Schreibt direkt in JSON-Datei (z.B. `digitaleAusgaengeMockData.json`)
### jsmock
-`GET /api/fake-cpl/updateDigitalOutputsHandler?id=3&value=1`
- ✍️ Ändert `.js` Datei per Regex und überschreibt `win_da_state = [...]`
### production
- ❌ Gerät entscheidet nur lesender Zugriff über `<script>`
- ⚙️ Änderung über CGI-URL z.B. `/digitalOutputs.html?OUT3=1`
---
## 🧪 Tipps
- Verwende `setInterval()` bei `jsmock` oder `production`, um regelmäßig `<script>` neu zu laden
- Bei `json`: nutze Redux-Thunk + Service → API → JSON
- `fetchDigitalOutputsService.ts` kapselt alle Unterschiede sauber ab
---
## 📁 Verzeichnisse
```
/mocks/api/SERVICE/ # JSON-Dateien
/public/CPLmockData/SERVICE/ # jsmock-Skripte
/public/CPL/SERVICE/ # Geräteplatzhalter
/pages/api/fake-cpl/ # GET/UPDATE API für jsmock
/pages/api/cpl/ # JSON- und Gerätelogik
/store/digitalOutputs/ # Redux-Slice & Thunk
```
## 🧩 Redux-Flow Übersicht (Mermaid)
```mermaid
flowchart TD
A[Component: DigitalOutputsWidget] --> B[useEffect]
B --> C[dispatch fetchDigitalOutputsThunk]
C --> D[fetchDigitalOutputsService]
D --> E{Moduswahl}
E -->|json| F[API: /api/cpl/fetchDigitalOutputsAPIHandler]
E -->|jsmock| G[<script> digitalOutputsMockData.js]
E -->|production| H[<script> /CPL/digitalOutputs.js]
F & G & H --> I[Reducer: digitalOutputsSlice]
I --> J[useSelector] --> A
```

View File

@@ -0,0 +1,91 @@
# 📦 Datenfluss-Dokumentation: Digitale Ausgänge (Lesen & Schreiben)
Dieses Dokument beschreibt die Architektur und Datenflüsse für das **Lesen** und **Updaten** der digitalen Ausgänge im CPL-System. Unterstützt werden folgende Modi:
- `json`: Entwicklung mit editierbarer JSON-Datei
- `jsmock`: Simulation durch `digitalOutputsMockData.js`
- `production`: Reale CPL-Hardware über CGI-Platzhalter
---
## 🔁 Lesen der digitalen Ausgänge
```mermaid
flowchart TD
A[DigitalOutputsWidget.tsx] --> B[useEffect]
B --> C[dispatch fetchDigitalOutputsThunk]
C --> D[fetchDigitalOutputsService]
D --> E{Moduswahl: NEXT_PUBLIC_CPL_MODE}
E -->|json| F1[GET /api/cpl/fetchDigitalOutputsHandler.ts]
F1 --> F2[digitalOutputsMockData.json]
E -->|jsmock| G1[GET /api/fake-cpl/fetchDigitalOutputsDeviceMockHandler]
G1 --> G2[digitalOutputsMockData.js → window.win_da_state]
E -->|production| H1[loadScript '/CPL?/CPL/SERVICE/digitalOutputs.js']
H1 --> H2[Platzhalter-basierte win_da_state]
F2 & G2 & H2 --> I[Redux: digitalOutputsSlice]
I --> J[useSelector] --> A
```
---
## ✍️ Updaten der digitalen Ausgänge
```mermaid
flowchart TD
A[DigitalOutputsWidget.tsx: onToggle] --> B[handleToggle id]
B --> C[Redux: setDigitalOutputs]
B --> D{Modus: production oder nicht}
D -->|production| E1[window.location.href = /CPL?digitalOutputs.html&DAS0X=1]
D -->|json oder jsmock| F1[POST /api/cpl/updateDigitalOutputsHandler]
F1 --> F2[Schreibe JSON oder JS-Datei]
F2 & E1 --> G[Aktualisierte Zustände am Gerät]
```
---
## 🌐 API-Endpunkte
| Route | Methode | Modus | Funktion |
| ------------------------------------------------------- | -------- | ------------- | ----------------------------- |
| `/api/cpl/fetchDigitalOutputsHandler.ts` | `GET` | `json` | Liest JSON-Datei |
| `/api/fake-cpl/fetchDigitalOutputsDeviceMockHandler.ts` | `GET` | `jsmock` | Liest Mock-JS-Datei |
| `/CPL?/CPL/SERVICE/digitalOutputs.js` | `SCRIPT` | `production` | Liefert Platzhalter vom Gerät |
| `/api/cpl/updateDigitalOutputsHandler.ts` | `POST` | `json/jsmock` | Speichert Statusänderung |
---
## 🧪 Beispiel-JSON (Mock)
```json
{
"win_da_state": [1, 0, 1, 0],
"win_da_bezeichnung": ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"]
}
```
---
## 📁 Verzeichnisse
```
/mocks/api/SERVICE/ → JSON-Dateien
/mocks/device-cgi-simulator/SERVICE/→ jsmock: digitalOutputsMockData.js
/public/CPL/SERVICE/ → production: digitalOutputs.js
/pages/api/cpl/ → JSON-/Update-Handler
/pages/api/fake-cpl/ → jsmock-API-Handler
/components/main/einausgaenge/ → UI-Komponente: DigitalOutputsWidget.tsx
```
---
## ✅ Status
- [x] Lesen funktioniert in allen Modi
- [x] Schreiben funktioniert in allen Modi (production nur über Redirect)
- [x] Redux-Integration ist vollständig

View File

@@ -0,0 +1,14 @@
{
"win_da_state": [
1,
0,
1,
0
],
"win_da_bezeichnung": [
"Ausgang1",
"Ausgang2",
"Ausgang3",
"Ausgang4"
]
}

View File

@@ -1,7 +1,7 @@
{ {
"win_da_state": [ "win_da_state": [
1, 1,
0, 1,
1, 1,
0 0
], ],

View File

@@ -0,0 +1,2 @@
win_da_state = [1, 1, 1, 0];
win_da_bezeichnung = ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"];

View File

@@ -1,2 +1,2 @@
win_da_state = [1, 0, 1, 0]; win_da_state = [1, 0, 1, 0];
win_da_bezeichnung = ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"]; win_da_bezeichnung = ["Ausgang11", "Ausgang2", "Ausgang3", "Ausgang4"];

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.417", "version": "1.6.418",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.417", "version": "1.6.418",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@iconify-icons/ri": "^1.2.10", "@iconify-icons/ri": "^1.2.10",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.417", "version": "1.6.418",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@@ -1,4 +1,4 @@
// /pages/api/cpl/analogeEingaengeAPIHandler.ts // /pages/api/cpl/getDigitalOutputsJsonHandler.ts
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import path from "path"; import path from "path";
@@ -13,7 +13,7 @@ export default async function handler(
"mocks", "mocks",
"api", "api",
"SERVICE", "SERVICE",
"digitaleAusgaengeMockData.json" "digitalOutputsMockData.json"
); );
try { try {

View File

@@ -1,4 +1,4 @@
// /pages/api/cpl/updateDigitalOutputs.ts // /pages/api/cpl/updateDigitalOutputshandler.ts
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
@@ -29,7 +29,7 @@ export default function handler(req, res) {
"mocks", "mocks",
"api", "api",
"SERVICE", "SERVICE",
"digitaleAusgaengeMockData.json" "digitalOutputsMockData.json"
); );
try { try {
@@ -46,9 +46,9 @@ export default function handler(req, res) {
filePath = path.join( filePath = path.join(
process.cwd(), process.cwd(),
"mocks", "mocks",
"js-simulator", "device-cgi-simulator",
"SERVICE", "SERVICE",
"digitaleAusgaengeMockData.js" "digitalOutputsMockData.js"
); );
const jsContent = const jsContent =

View File

@@ -0,0 +1,47 @@
// /pages/api/fake-cpl/SERVICE/fetchDigitalOutputsAPIHandler.ts
import fs from "fs";
import path from "path";
export default function handler(req, res) {
const filePath = path.join(
process.cwd(),
"mocks",
"device-cgi-simulator",
"SERVICE",
"digitalOutputsMockData.js"
);
try {
const content = fs.readFileSync(filePath, "utf-8");
const win_da_state = extractArray(content, "win_da_state");
const win_da_bezeichnung = extractArray(content, "win_da_bezeichnung");
return res.status(200).json({
win_da_state,
win_da_bezeichnung,
});
} catch (err) {
console.error("❌ Fehler beim Lesen der JS-Mock-Datei:", err);
return res.status(500).json({ error: "Fehler beim Lesen der Mock-Datei" });
}
}
// 🔧 Hilfsfunktion: Extrahiert Array aus JS-Datei
function extractArray(content: string, varName: string): any[] {
const match = content.match(
new RegExp(`${varName}\\s*=\\s*\\[(.*?)\\];`, "s")
);
if (!match) {
console.warn(`⚠️ ${varName} nicht gefunden`);
return [];
}
try {
return JSON.parse(`[${match[1]}]`);
} catch (e) {
console.warn(`⚠️ Fehler beim Parsen von ${varName}:`, e);
return [];
}
}

View File

@@ -0,0 +1,48 @@
// /pages/api/fake-cpl/SERVICE/updateDigitalOutputsHandler.ts
import fs from "fs";
import path from "path";
export default function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Nur GET erlaubt" });
}
const id = parseInt(req.query.id as string);
const value = parseInt(req.query.value as string);
const filePath = path.join(
process.cwd(),
"mocks",
"device-cgi-simulator",
"SERVICE",
"digitalOutputsMockData.js"
);
try {
let content = fs.readFileSync(filePath, "utf-8");
const match = content.match(/win_da_state\s*=\s*\[(.*?)\];/s);
const currentState = match ? JSON.parse(`[${match[1]}]`) : [];
if (isNaN(id) || isNaN(value) || id < 1 || id > currentState.length) {
return res
.status(400)
.json({ error: `Ungültige Parameter: id=${id}, value=${value}` });
}
// Wert aktualisieren
currentState[id - 1] = value;
const updatedContent =
`win_da_state = [${currentState.join(", ")}];\n` +
`win_da_bezeichnung = ["A1", "A2", "A3", "A4"]; // fest für Demo`;
fs.writeFileSync(filePath, updatedContent, "utf-8");
res.status(200).json({ success: true, win_da_state: currentState });
} catch (err) {
console.error("❌ Fehler beim Schreiben der Mock-Datei:", err);
res.status(500).json({ error: "Fehler beim Schreiben der Mock-Datei" });
}
}

View File

@@ -1,8 +1,4 @@
// /public/CPL/SERVICE/da.js // /public/CPL/SERVICE/digitalOutputs.js
//var win_da_state=[<%=DES80%>,<%=DES81%>,<%=DES82%>,<%=DES83%>]; // alte Platzhalter aber ich bekome noch die Werte var win_da_state=[<%=DAS01%>,<%=DAS02%>,<%=DAS03%>,<%=DAS04%>];
//var win_da_bezeichnung=["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"]; var win_da_bezeichnung=[<%=DAN01%>,<%=DAN02%>,<%=DAN03%>,<%=DAN04%>];
var win_da_state=[<%=DAS01%>,<%=DAS02%>,<%=DAS03%>,<%=DAS04%>]; //neu aber ein Leere Array mit 4 indizes , Backend ist noch in Arbeit
var win_da_bezeichnung=[<%=DAN01%>,<%=DAN02%>,<%=DAN03%>,<%=DAN04%>]; //neu aber ein Leere Array mit 4 indizes , Backend ist noch in Arbeit

View File

@@ -1,10 +1,10 @@
// /services/fetchDigitalOutputsService.ts // /services/fetchDigitalOutputsService.ts
export const fetchDigitalOutputsService = async () => { export const fetchDigitalOutputsService = async () => {
const mode = process.env.NEXT_PUBLIC_CPL_MODE; const mode = process.env.NEXT_PUBLIC_CPL_MODE;
if (mode === "json") { if (mode === "json") {
const res = await fetch("/api/cpl/digitalOutputsAPIHandler"); const res = await fetch("/api/cpl/getDigitalOutputsJsonHandler");
if (!res.ok) if (!res.ok)
throw new Error("❌ Fehler beim Laden der digitalen Ausgänge (JSON)"); throw new Error("❌ Fehler beim Laden der digitalen Ausgänge (JSON)");
@@ -12,6 +12,65 @@ export const fetchDigitalOutputsService = async () => {
const state = data.win_da_state; const state = data.win_da_state;
const labels = data.win_da_bezeichnung; const labels = data.win_da_bezeichnung;
if (!Array.isArray(state)) {
console.warn("⚠️ win_da_state fehlt oder ist ungültig in json:", state);
return [];
}
return state.slice(0, 4).map((status: number, index: number) => ({
id: index + 1,
label:
Array.isArray(labels) && labels[index]
? labels[index]
: `Ausgang ${index + 1}`,
status: status === 1,
}));
}
if (mode === "jsmock") {
const res = await fetch(
"/api/fake-cpl/SERVICE/getDigitalOutputsDeviceMockHandler"
);
if (!res.ok)
throw new Error("❌ Fehler beim Laden der digitalen Ausgänge (jsmock)");
const data = await res.json();
const state = data.win_da_state;
const labels = data.win_da_bezeichnung;
if (!Array.isArray(state)) {
console.warn("⚠️ win_da_state fehlt oder ist ungültig in jsmock:", state);
return [];
}
return state.slice(0, 4).map((status: number, index: number) => ({
id: index + 1,
label:
Array.isArray(labels) && labels[index]
? labels[index]
: `Ausgang ${index + 1}`,
status: status === 1,
}));
}
if (mode === "production") {
// Hier erwarten wir eine echte Datei aus /public/CPL/
const scriptUrl = "/CPL?/CPL/SERVICE/digitalOutputs.js";
await new Promise<void>((resolve, reject) => {
const script = document.createElement("script");
script.src = scriptUrl;
script.async = true;
script.onload = () => resolve();
script.onerror = () =>
reject("❌ Fehler beim Laden der digitalOutputs.js");
document.body.appendChild(script);
});
const win = window as any;
const state = win.win_da_state;
const labels = win.win_da_bezeichnung;
if (!Array.isArray(state)) { if (!Array.isArray(state)) {
console.warn("⚠️ win_da_state fehlt oder ist ungültig:", state); console.warn("⚠️ win_da_state fehlt oder ist ungültig:", state);
return []; return [];
@@ -27,44 +86,6 @@ export const fetchDigitalOutputsService = async () => {
})); }));
} }
// jsmock oder production console.warn(`⚠️ Unbekannter CPL-Modus: ${mode}`);
let scriptUrl = ""; return [];
if (mode === "production") {
scriptUrl = "/CPL?/CPL/SERVICE/digitalOutputs.js";
} else if (mode === "jsmock") {
scriptUrl = "/CPLmockData/SERVICE/digitalOutputsMockData.js";
} else {
console.warn(
`⚠️ fetchDigitalOutputsFromScript wird nur in 'jsmock' oder 'production' verwendet (aktueller Modus: ${mode})`
);
return [];
}
await new Promise<void>((resolve, reject) => {
const script = document.createElement("script");
script.src = scriptUrl;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject("❌ Fehler beim Laden der digitalOutputs.js");
document.body.appendChild(script);
});
const win = window as any;
const state = win.win_da_state;
const labels = win.win_da_bezeichnung;
if (!Array.isArray(state)) {
console.warn("⚠️ win_da_state fehlt oder ist ungültig:", state);
return [];
}
return state.slice(0, 4).map((status: number, index: number) => ({
id: index + 1,
label:
Array.isArray(labels) && labels[index]
? labels[index]
: `Ausgang ${index + 1}`,
status: status === 1,
}));
}; };