[{"id":"a27e26d3bd646100","type":"group","z":"e24fe565e148a8ed","name":"fenecon Websocket","style":{"stroke":"#0070c0","fill":"#bfdbef","label":true,"label-position":"ne","color":"#001f60"},"nodes":["6e4f7b36eff7c99c","9b6ce7db99ac15f6","0892a2ce8909c01f","3d24a6a22cabc7f2","63490d20297dc38d","553a489bc54345f8","5d73fc162e8cbf8e","dad15ad8cd7b1aca","8709f8060302d15e","954330562d646059"],"x":134,"y":75,"w":812,"h":410},{"id":"6e4f7b36eff7c99c","type":"function","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"fenecon ws connect","func":"\n\n/*\npayload = {fn: 0 //fungtion0..2 \n \n}\n\nnode functions\n0 - init \n1 - disconnect\n2 - subscribe\n3 - unsubscribe\n*/\n\nlet nFunction = msg.fn;\n\n\nconst WebSocketClient = websocket.client;\n\n// config\nconst username = msg.payload.username;\nconst password = msg.payload.password;\nconst fems_ip = msg.payload.fems_ip;\nconst fems_port = msg.payload.fems_port;\nconst fems_request_channels = msg.payload.fems_request_channels;\n\n// Static uuids for request\nconst uuid_str_auth = uuid.v4();\nconst uuid_str_getEdge = uuid.v4();\nconst uuid_str_getEdgeConfig_payload = uuid.v4();\nconst uuid_str_getEdgeConfig_request = uuid.v4();\nconst uuid_str_subscribe_payload = uuid.v4();\nconst uuid_str_subscribe_request = uuid.v4();\nconst uuid_str_logout = uuid.v4();\n\nconst requestKey = 0;\nconst requestType = 1;\nconst requestUnit = 2;\nconst requestName = 3;\nconst requestCalculation = 4;\nconst requestValues = 5;\n\nconst calculationInterval = 30; // 30sec\n\n// global process variable\nlet actualDataCount = 0;\nlet fems_components = {};\n\n\nfunction get_request_channels() {\n let channels = []\n for (let i = 0; i < fems_request_channels.length; i++) {\n const channelKey = fems_request_channels[i][requestKey];\n channels.push(channelKey);\n };\n return channels;\n};\n\nconst request_channels = JSON.stringify(get_request_channels());\n\n\n// JSON request template\nconst json_auth_passwd =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_auth},\n \"method\": \"authenticateWithPassword\",\n \"params\": {\n \"username\": ${username},\n \"password\": ${password}\n }\n }`;\n\nconst json_get_edge =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_getEdge},\n \"method\": \"getEdge\",\n \"params\": {\n \"edgeId\": \"0\"\n }\n}`;\n\nconst json_get_edgeconfig_payload =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_getEdgeConfig_payload},\n \"method\": \"getEdgeConfig\",\n \"params\": {\" \": \" \"}\n }`;\n\nconst json_get_edgeconfig_req =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_getEdgeConfig_request},\n \"method\": \"edgeRpc\",\n \"params\": {\n \"edgeId\": \"0\",\n \"payload\": ${json_get_edgeconfig_payload}\n }\n }`;\n\nconst json_subscribe_payload =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_subscribe_payload},\n \"method\": \"subscribeChannels\",\n \"params\": {\n \"count\": \"0\",\n \"channels\": ${request_channels}\n }\n}`;\n\nconst json_subscribe_req =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_subscribe_request},\n \"method\": \"edgeRpc\",\n \"params\": {\n \"edgeId\": \"0\",\n \"payload\": ${json_subscribe_payload}\n }\n }`;\n\nconst json_logout =\n `{\n \"jsonrpc\": \"2.0\",\n \"id\": ${uuid_str_auth},\n \"method\": \"logout\",\n \"params\": {}\n }`;\n\n// processing\nfunction calculateValue(inputArray, calculateFunktion) {\n switch (calculateFunktion) {\n case 'avarange':\n const sum = inputArray.reduce((acc, val) => acc + val, 0);\n return sum / inputArray.length;\n case 'last':\n return inputArray[inputArray.length - 1];\n default:\n throw new Error('Unsupported calculation function');\n }\n}\n\nfunction dataProzess(receivedData, calculationInterval) {\n actualDataCount++;\n\n for (let i = 0; i < fems_request_channels.length; i++) {\n const channelKey = fems_request_channels[i][requestKey];\n // @ts-ignore\n if (receivedData[channelKey] !== undefined) {\n // @ts-ignore\n fems_request_channels[i][requestValues].push(receivedData[channelKey]);\n }\n }\n\n if (actualDataCount === calculationInterval) {\n\n for (let i = 0; i < fems_request_channels.length; i++) {\n const calculatedValue = calculateValue(fems_request_channels[i][requestValues], fems_request_channels[i][requestCalculation]);\n fems_request_channels[i][requestValues] = [calculatedValue]; // Replace array with calculated value\n\n // fill type, Unit\n // split -> \"_sum/ProductionActivePower\" -> [\"_sum\", \"ProductionActivePower\", \"\"]\n let keySplit = fems_request_channels[i][requestKey].split(/\\/(.*)/s);\n\n // eg. fems_components._sum.channels.ProductionActivePower\n let channel = fems_components[keySplit[0]][\"channels\"][keySplit[1]];\n\n fems_request_channels[i][requestType] = ((channel.type === \"INTEGER\") || (channel.type === \"LONG\")) ? \"number\" : \"\";\n fems_request_channels[i][requestUnit] = (channel.unit === \"Wh_Σ\") ? \"Wh\" : channel.unit;\n\n msg.topic = \"node-red.0/fems/\" + fems_request_channels[i][requestKey];\n msg.stateType = fems_request_channels[i][requestType];\n msg.stateUnit = fems_request_channels[i][requestUnit];\n msg.stateName = fems_request_channels[i][requestName];\n\n msg.payload = fems_request_channels[i][requestValues][0]; // value\n node.send([null, null, msg]);\n\n };\n // send calculated datas\n msg.payload = fems_request_channels;\n node.send([msg, null]);\n\n actualDataCount = 0; // Reset counter\n }\n}\n\n// helper\nconst mydTime = () => {\n return ((new Date()).toLocaleDateString('de-DE', {\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric'\n }));\n};\n\n\nfunction sendWithDelay(connection, message, delay) {\n setTimeout(() => {\n connection.send(message);\n }, delay);\n}\n\nif (nFunction == 0) { // only if start\n var client = new WebSocketClient();\n flow.set(\"fems_client\", client);\n flow.set(\"fems_isConnected\", false);\n} else { // all other requests\n var client = flow.get(\"fems_client\");\n}\n\nclient.on('connectFailed', function (error) {\n let cTime = mydTime();\n node.status({ fill: 'red', shape: \"dot\", text: 'connectFailed:' + cTime });\n console.log('Connect Error: ' + error.toString());\n flow.set(\"fems_isConnected\", false);\n});\n\nclient.on('connect', function (connection) {\n let cTime = mydTime();\n node.status({ fill: 'green', shape: \"dot\", text: 'connected:' + cTime });\n console.log('connection established!');\n flow.set(\"fems_Connection\", connection);\n flow.set(\"fems_isConnected\", true);\n\n // auth\n console.log('Fenecon opened connection -> send authenticate');\n sendWithDelay(connection, json_auth_passwd, 0);\n\n // get edge\n console.log('Fenecon opened connection -> send getEdge');\n sendWithDelay(connection, json_get_edge, 500);\n\n // get edgeConfig\n console.log('Fenecon opened connection -> send getEdge configuration');\n sendWithDelay(connection, json_get_edgeconfig_req, 1000);\n\n // Subscribe\n console.log('Fenecon opened connection -> send subscribe');\n sendWithDelay(connection, json_subscribe_req, 1500);\n\n\n connection.on('error', function (error) {\n node.status({ fill: 'red', shape: \"dot\", text: 'Connection error:' + cTime });\n console.log(\"Connection error: \" + error.toString());\n });\n\n connection.on('close', function () {\n node.status({ fill: 'yellow', shape: \"dot\", text: 'Connection closed!:' + cTime });\n setTimeout(() => {\n msg.payload = Date.now();;\n node.send([null, null, null, msg]);\n }, 60000); //1min\n console.log('Connection closed!');\n });\n\n connection.on('message', function (message) {\n // console.log(\"Current time on server is: '\" + message.utf8Data + \"'\");\n let received = JSON.parse(message.utf8Data);\n\n if (received.params && received.params.payload && received.params.payload.params) {\n dataProzess(received.params.payload.params, calculationInterval);\n } else {\n\n // is edgeConfig request? save edgeConfig\n if (received.id === uuid_str_getEdgeConfig_request) {\n fems_components = received.result.payload.result.components;\n }\n\n msg.payload = received;\n node.send([null, msg]);\n };\n\n });\n\n\n});\n\n\nlet isConnected = flow.get(\"fems_isConnected\");\nif (nFunction == 0) {\n let fems_uri = `ws://${fems_ip}:${fems_port}/websocket`;\n client.connect(fems_uri);\n} else if (nFunction == 1 && isConnected) { // logout\n let connection = flow.get(\"fems_Connection\");\n sendWithDelay(connection, json_logout, 0);\n let cTime = mydTime();\n node.status({ fill: 'yellow', shape: \"dot\", text: 'logged out: ' + cTime });\n flow.set(\"fems_isConnected\", false);\n} ","outputs":4,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[{"var":"uuid","module":"uuid"},{"var":"websocket","module":"websocket"}],"x":480,"y":320,"wires":[["553a489bc54345f8"],["63490d20297dc38d"],["3d24a6a22cabc7f2","5d73fc162e8cbf8e"],["dad15ad8cd7b1aca"]],"outputLabels":["subscribed raw data","other recived data","dp to create or set","connection close -> restart after 1m"]},{"id":"9b6ce7db99ac15f6","type":"inject","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"Start","props":[{"p":"payload"},{"p":"fn","v":"0","vt":"num"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":220,"wires":[["dad15ad8cd7b1aca"]]},{"id":"0892a2ce8909c01f","type":"comment","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"`Konfiguration \\n Verbindungseinstellungen \\n abbonomierte Kanäle `","info":"","x":490,"y":140,"wires":[]},{"id":"3d24a6a22cabc7f2","type":"debug","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"dp to create or update","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":800,"y":360,"wires":[]},{"id":"63490d20297dc38d","type":"debug","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"other response data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":800,"y":320,"wires":[]},{"id":"553a489bc54345f8","type":"debug","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"subsribed raw data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":790,"y":280,"wires":[]},{"id":"5d73fc162e8cbf8e","type":"ioBroker out","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"node-red.0/fems/...","topic":"","ack":"true","autoCreate":"true","stateName":"","role":"","payloadType":"","readonly":"","stateUnit":"","stateMin":"","stateMax":"","x":790,"y":420,"wires":[]},{"id":"dad15ad8cd7b1aca","type":"change","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"set configuration","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"username\“:\“xxxxx\“,\“password\“:\“xxxxx\“,\“fems_ip\":\"192.168.xxx.xxx\“,\“fems_port\":\"8085\",\"fems_request_channels\":[[\"_sum/ProductionActivePower\",\"\",\"\",\"Total production; always positive\",\"avarange\",[]],[\"_sum/GridActivePower\",\"\",\"\",\"Grid exchange power. -sell +by \",\"avarange\",[]],[\"_sum/EssActivePower\",\"\",\"\",\"AC-side power of Storage System -charge +discharge\",\"avarange\",[]],[\"_sum/EssDischargePower\",\"\",\"\",\"AC-side power of Storage System -charge +discharge\",\"avarange\",[]],[\"_sum/ConsumptionActivePower\",\"\",\"\",\"Wirkleistung alle Verbraucher\",\"avarange\",[]],[\"_sum/EssActiveChargeEnergy\",\"\",\"\",\"charge energy counter\",\"last\",[]],[\"_sum/EssActiveDischargeEnergy\",\"\",\"\",\"discharge energy counter\",\"last\",[]],[\"_sum/EssDcChargeEnergy\",\"\",\"\",\"dc charge energy counter\",\"last\",[]],[\"_sum/EssDcDischargeEnergy\",\"\",\"\",\"dc discharge energy counter\",\"last\",[]],[\"_sum/ProductionActiveEnergy\",\"\",\"\",\"aktive Energy Produktion\",\"last\",[]],[\"_sum/ConsumptionActiveEnergy\",\"\",\"\",\"Energy alle Verbraucher\",\"last\",[]],[\"charger0/ActualPower\",\"\",\"\",\"Ostdach Power\",\"avarange\",[]],[\"charger0/ActualEnergy\",\"\",\"\",\"Ostdach Energie\",\"last\",[]],[\"charger0/State\",\"\",\"\",\"Ostdach Status 0:Ok, 1:Info, 2:Warning, 3:Fault\",\"last\",[]],[\"charger1/ActualPower\",\"\",\"\",\"Westdach Power\",\"avarange\",[]],[\"charger1/ActualEnergy\",\"\",\"\",\"Westdach Energie\",\"last\",[]],[\"charger1/State\",\"\",\"\",\"Westdach Status 0:Ok, 1:Info, 2:Warning, 3:Fault\",\"last\",[]],[\"evcs0/ChargePower\",\"\",\"\",\"E-Auto/Ladeleistung\",\"avarange\",[]],[\"evcs0/EnergySession\",\"\",\"\",\"E-Auto/letzte_Ladung\",\"last\",[]],[\"evcs0/ActiveConsumptionEnergy\",\"\",\"\",\"aktueller Energiezähler\",\"last\",[]],[\"evcs0/ChargeState\",\"\",\"\",\"0:Not charging, 1:Charging, 2:Decreasing, 3:Increasing, 4:Waiting for available power\",\"last\",[]],[\"ess0/Soc\",\"\",\"\",\"SOC Speicher\",\"last\",[]],[\"ess0/State\",\"\",\"\",\"Speicher Status (0:Ok, 1:Info, 2:Warning, 3:Fault)\",\"last\",[]],[\"batteryInverter0/AirTemperature\",\"\",\"\",\"\",\"last\",[]],[\"batteryInverter0/BmsPackTemperature\",\"\",\"\",\"\",\"last\",[]],[\"batteryInverter0/RadiatorTemperature\",\"\",\"\",\"\",\"last\",[]],[\"batteryInverter0/WbmsTemperature\",\"\",\"\",\"\",\"last\",[]],[\"_sum/State\",\"\",\"\",\"Systems Status (0: Ok, 1:Info, 2:Warning, 3:Fault)\",\"last\",[]]]}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":220,"wires":[["6e4f7b36eff7c99c"]]},{"id":"8709f8060302d15e","type":"inject","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"Logout","props":[{"p":"payload"},{"p":"fn","v":"1","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":260,"wires":[["dad15ad8cd7b1aca"]]},{"id":"954330562d646059","type":"comment","z":"e24fe565e148a8ed","g":"a27e26d3bd646100","name":"`Bei Connection closed\\n Neuverbindung in 1min \\n über Ausgang 4 `","info":"","x":480,"y":420,"wires":[]}]