Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Praktische Anwendungen (Showcase)
    4. node-red Kommunikation mit einem fenecon Wechselrichter

    NEWS

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    • Monatsrückblick - April 2025

    node-red Kommunikation mit einem fenecon Wechselrichter

    This topic has been deleted. Only users with topic management privileges can see it.
    • R
      rewenode last edited by

      Da ich eine zu meinem Post eine Anfrage mit der Bitte um Weitergabe hatte, will ich meine Version hier mal zur Verfügung stellen und rudimentär beschreiben.

      Wie im o.g. Thread zu sehen, gibt es mehrere Möglichkeiten mit einem fenecon Wechselrichter zu kommunizieren um die Daten in iob weiter zu verwenden.
      Ich will die hier nicht vergleichen, schon weil ich keine Erfahrungen mit Ausnahme von Modbus habe. Und die Schnittstelle lieferte nicht alle von mir benötigten Daten.
      Warum also sollte man sich das antun, dafür einen node-red flow einzusetzen.

      Da gibt es nicht viele Gründe. In meinem Fall sind das:

      • ich wollte die vollständige Kontrolle über die Daten haben, die ich vom Wechselrichter benötige
      • ich will die empfangenen Daten ohnehin mit node-red weiterverarbeiten
      • weil mir der Umgang mit node-red inzwischen recht einfach von der Hand geht

      Und nicht zuletzt habe ich mir angewöhnt iob-Adapter nur zu benutzen, wenn sich das Problem nicht mit vertretbarem Aufwand durch die bereits vorhandenen Möglichkeiten erledigen läßt, was sicher an der begrenzten Leistungsfähigkeit meiner Hardware liegt.

      Also alles Gründe, die für die meisten unter Euch sicher nicht relevant sind. Und deshalb auch eine Warnung vorab:
      Wer keinerlei Erfahrung mit node-red, JSON und Javascript hat sollte die Finger davon lassen!

      Ok, was tut nun der flow?

      • Er baut eine Websocket-Verbindung zum fenecon-Wechselrichter (nur getestet im internen Netzwerk) auf und lässt sich die abbonierten Daten per Websocket schicken. Also genau das, was das fenecon Webinterface auch macht.
      • die empfangenen Daten werden aufbereitet und berechnet.
        Der Wechselrichter sendet die Daten ca. sekündlich. Da ich diese Geschwindigkeit nicht benötige und iob nicht unnötig belasten wollte,
        werden die Daten gesammelt und erst nach einer administrierbaren Zeit (const calculationInterval = 30;) verarbeitet.
        Zur Zeit könnten für die Verarbeitung 2 Methoden pro Channel eingestellt werden:
        - avarange - bildet den Durchschnitt der innerhalb von calculationInterval erhaltenen Daten. Das macht Sinn bei allen Leistungswerten
        - last - es wir der letzte empfangene Wert genommen. Das macht Sinn bei den Arbeitswerten (Zähler) Temperaturen, Fehler etc.
        Hier interessiert i.d.R. immer der letzte Wert.
        Weitere Kalkulationen können bei Bedarf in der Funktion calculateValue() implementiert werden.
      • Nach der Kalkulation werden die Daten mit einem ioBroker out - Node an iob gesendet. Die Node aktualisiert die entsprechenden Datenpunkte. Ist der noch nicht vorhanden, wird er angelegt. Es müssen also bei Erstinbetriebnahme innerhalb von iob keine Datenpunkte angelegt werden.

      Vorbereitung
      Es wird der node-red Adapter von iob verwendet, indem die ioBroker out - Node bereits enthalten ist. iob und der node-red Adapter sollten also aktuell sein. Ich verwende js-controller 5.0.19 und node-red Adapter v5.2.1
      Prinzipiell arbeitet der flow zwar (bis auf den ioBroker out - Node) auch mit einem externen eigenständigen node-red. Allerdings muss man sich dann selber Gedanken machen, ob und wie man die Daten in den iob bekommt. Ich erwähne das deshalb, weil man die Daten u.U. vlt. nur an eine Datenbank übergeben will.
      Der flow verwendet 2 JS Bibliotheken uuid und websocket. Diese müssen NICHT installiert werden! Das macht node-red in meinem flow automatisch (zumindest in aktuellen Versionen)
      Achtung! Der erste deplay des flows kann deshalb etwas länger dauern, da hier die benötigten Bibliotheken installiert werden.

      Hier der exportierte flow zum Import

      [{"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":[]}]
      

      Den flow importieren ABER noch nicht deployen, da er erst konfiguriert werden muss.

      flow.png

      Konfiguration
      Die Grundkonfiguration geschieht im JSON-Format in einem change-node.

      Config.png

      Hier ein Beispiel JSON aus dem change-node.

      {
         "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",
                 []
             ]
         ]
      }
      

      Der Aufbau ist wie folgt:

      {
         "username": "xxxxx",   	// entspricht dem user der lokalen Wechseleichter-Website
         "password": "xxxxx",		// entspricht dem password der lokalen Wechselrichter-Website
         "fems_ip": "192.168.xxx.xxx",  // lokale ip Adresse des Wechselrichters
         "fems_port": "8085",		// websocket Port
         "fems_request_channels": []	// Array der abbonierten Kanäle
      }
      

      Aufbau eines abbonierten Kanals:

      [
          "_sum/EssDischargePower",         // original channel name (siehe Hinweise)
          "",	  // Datentyp wird i.d.R. von den gesendeten Daten übernommen 
          "",   //  Unit wird i.d.R. von den gesendeten Daten übernommen 
          "AC-side power of Storage System -charge +discharge",  // iob Datenpunkt Namensfeld
          "avarange", // Berechnungsmethode (avarange - es wird der Durchscnitt gebildet, last - letzter Wert wird verwendet) 	
          []	// wird intern zum speichern der werte benutzt
      ],
      

      Konfiguration Kalkulationsinterval
      Standardmäßig werden die iob - Datenpunkte aller 30 sek aktualisiert. Soll die Zeitspanne geändert werden, kann die in der function-node mit der Variablen

      const calculationInterval = 30;  // 30sec
      

      gemacht werden.

      Die iob Datenpunkte
      Die Datenpunkte werden innerhalb von iob in node-red.0/fems angelegt.
      iob datenpunkte.png

      Hinweise
      Die Idee und die Infos habe ich vom
      original HA-Fenecon-Adapter
      Die Websocket Schnittstelle ist weder von Fenecon, noch von
      OpenEMS voll dokumentiert.
      Man kann sich die Infos aber einigermaßen bei OpenEMS zusammenlesen.

      Hab das Ganze zuerst als Pythonscript gebastelt und das Konzept dann als JS-Script in Node-Red umgesetzt.

      Will man einen Eindruck bekommen, welche Datenpunkte prinzipiell möglich sind, kann man das über die debug-Ausgabe erfahren.
      Beim Starten der Verbindung wird u.a. die Methode:
      getEdgeConfig aufgerufen. Diese gibt ein JSON sämtlicher installierter Komponenten und deren Channels zurück. Ist der debug-Node „other response data“ aktiviert, wird hier beim Start der Verbindung ein JSON mit allen zur Verfügung stehenden Components und deren Channels ausgegeben.
      Hier kopiert man sich am besten das Object „components“ oder das channels-object eines bestimmten channels.
      Hier finden sich die Daten des channels, die dann für die Konfiguration der zu abonnierenden channels genutzt werden können.

      component channels.png

      Kurzinfo zum Flow
      Der Start Input-node kann zum manuellen Start des flows verwendet werden. Er ist aber so eingestellt, dass er nach dem deplay des flows auch automatisch startet.
      Der Logout Input-node kann dafür benutzt werden, die Verbindung zu beenden. Was i.d.R. nie nötig ist, es sei denn man bastelt an dem Flow rum.
      Über das change-node wird lediglich beim Start/Restart das config-json in das function-node injiziert. Dieses node wird nur verwendet um die Konfiguration an einer separaten Stelle durchführen zu können.
      Die eigentliche Funktionalität ist komplett als Javascript im function-node implementiert. Hier gibt es 4 Ausgänge:

      1. Ausgabe der kalkulierten abbonierten Daten als Array. Nur für debug-Zwecke oder zur evtl. Weiterverarbeitung
      2. Ausgabe der raw response Daten. Nur für debug-Zwecke oder zur evtl. Weiterverarbeitung
      3. Ausgabe der einzelnen channel Daten, aufbereitet als iob Datenpunkt daten
      4. Sollte die Verbindung mal Abbrechen, wird sie über diesen Ausgang neu gestartet

      Das sollte es mal als grundsätzliche Information sein.

      Gruß
      Reiner

      1 Reply Last reply Reply Quote 1
      • First post
        Last post

      Support us

      ioBroker
      Community Adapters
      Donate

      482
      Online

      31.7k
      Users

      79.7k
      Topics

      1.3m
      Posts

      fenecon node-red websocket
      1
      1
      284
      Loading More Posts
      • Oldest to Newest
      • Newest to Oldest
      • Most Votes
      Reply
      • Reply as topic
      Log in to reply
      Community
      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
      The ioBroker Community 2014-2023
      logo