[ { "id": "ksem_modbus_read", "type": "modbus-read", "z": "flow_ortsnetz", "name": "KSEM Register lesen", "topic": "", "showStatusActivities": false, "logIOActivities": false, "showErrors": true, "showWarnings": true, "unitid": "1", "dataType": "HoldingRegister", "adr": "58", "quantity": "12", "rate": "10", "rateUnit": "s", "delayOnStart": false, "startDelayTime": "", "server": "", "useIOComponent": false, "emptyPayloadOnFailure": false, "x": 140, "y": 120, "wires": [ [ "ksem_api_parser" ], [] ] }, { "id": "ksem_api_parser", "type": "function", "z": "flow_ortsnetz", "name": "Payload für API bauen", "func": "const buffer = Buffer.from(msg.buffer);\n\n// 1. Modbus-Buffer auslesen (KSEM verwendet Big Endian)\nconst frequenz = buffer.readUInt32BE(0) * 0.001;\nconst voltL1 = buffer.readUInt32BE(12) * 0.001; // Offset 12 Bytes (6 Register)\nconst voltL2 = buffer.readUInt32BE(16) * 0.001; // Offset 16 Bytes (8 Register)\nconst voltL3 = buffer.readUInt32BE(20) * 0.001; // Offset 20 Bytes (10 Register)\n\n// 2. Feste Standortdaten eures Kollegen (HIER ANPASSEN)\nconst LATITUDE = 52.520008; \nconst LONGITUDE = 13.404954;\n\n// 3. Payload exakt nach API-Spezifikation zusammensetzen\nmsg.payload = {\n \"observed_at\": new Date().toISOString(),\n \"latitude\": LATITUDE,\n \"longitude\": LONGITUDE,\n \"l1_v\": Math.round(voltL1 * 10) / 10,\n \"l2_v\": Math.round(voltL2 * 10) / 10,\n \"l3_v\": Math.round(voltL3 * 10) / 10,\n \"grid_frequency_hz\": Math.round(frequenz * 100) / 100,\n \"smartmeter_model\": \"Kostal KSEM\",\n \"integration_version\": \"node-red-ksem-1.0.0\"\n};\n\n// HTTP Header setzen\nmsg.headers = {\n \"Content-Type\": \"application/json\"\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 380, "y": 120, "wires": [ [ "ortsnetz_http_post" ] ] }, { "id": "ortsnetz_http_post", "type": "http request", "z": "flow_ortsnetz", "name": "API HTTP POST", "method": "POST", "ret": "obj", "url": "https://ortsnetz-auslastung.de", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 600, "y": 120, "wires": [ [ "ortsnetz_response_handler" ] ] }, { "id": "ortsnetz_response_handler", "type": "function", "z": "flow_ortsnetz", "name": "Rückmeldung auswerten", "func": "// Status-Code der HTTP-Anfrage prüfen\nconst statusCode = msg.statusCode;\n\nif (statusCode === 202) {\n // Erfolgreich übertragen\n const ampel = msg.payload.status;\n \n // Status-Text unter dem Node-RED Knoten anzeigen\n node.status({fill:\"green\", shape:\"dot\", text: `Gesendet! Gesamt-Ampel: ${ampel.overall}`});\n \n // optional: Hier kann der Kollege z.B. Logik für smarte Verbraucher triggern,\n // falls die Ampel auf \"yellow\" oder \"red\" springt.\n msg.payload = {\n erfolgreich: true,\n ampel_gesamt: ampel.overall,\n ampel_l1: ampel.l1,\n ampel_l2: ampel.l2,\n ampel_l3: ampel.l3\n };\n} else if (statusCode === 422) {\n node.status({fill:\"red\", shape:\"ring\", text: \"Validierungsfehler (422)\"});\n node.error(\"API Fehler 422: Validierung fehlgeschlagen. Wertebereiche prüfen.\", msg);\n} else if (statusCode === 403) {\n node.status({fill:\"red\", shape:\"dot\", text: \"Standort gesperrt (403)\"});\n node.error(\"API Fehler 403: Dieser Standort ist gesperrt.\", msg);\n} else {\n node.status({fill:\"yellow\", shape:\"ring\", text: `Fehler: ${statusCode}`});\n}\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 120, "wires": [ [ "debug_out" ] ] }, { "id": "debug_out", "type": "debug", "z": "flow_ortsnetz", "name": "Debug Output", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1070, "y": 120, "wires": [] } ]