- Home
- Deutsch
- Entwicklung
- Bosswerk MI600 Adapter
Bosswerk MI600 Adapter
-
@painless Hallo, ich bin auch sehr daran interessiert, die Daten des Bosswerk-Wechselrichters direkt ohne Cloud auszulesen. Kannst/willst du mir kurz den Stand deiner Versuche mitteilen, dann kann ich evtl. auch ein bisschen am "Geheimnis" kramen.
-
Gerne. Voraussetzung - node red adapter.
Du musst in den Node-Red Einstellungen "Erstellung von Fremdobjekten zulassen" aktivieren, damit die Datenpunkte erstellt werden.
Alle bosswerk relevanten pv Daten sind in der 547-msg length drin. Screen mal die 60ér und 73ér msg. Da wird Deine ip und Wifi Adresse übertragen...
[
{
"id": "6e802f1553b18149",
"type": "subflow",
"name": "JSON or Obj to IOBroker",
"info": "# Creates an IOBroker tree\n\nThis node creates an IOBroker tree out of an Java-Object or JSON String.\n\nThe object tree will be created under 0_userdata.0\nIn addition to the JSON-String or Java Object asmsg.payload
it is necessary to specify amsg.top
properity in addition to the msg-Object.\n\nThe object tree will be created under 0_userdata.0\n\nExistingmsg.topic
entries will be deleted.\nAn iobroker-out node has to be appended to this subflow node. It is not part of the subflow itself. No topic should be specified in the iobroker out node.\n\nIsmsg.top
property isn't defined, thetop
property of the subflow-node is used. \n\nIn the properties of the subflow node a new propertykeepTopic
has been added. Default is false to keep the current behaviour. If set to true then the originial topic will be placed between thetop
property of the subflow node and the property of the analyzed JSON object.\n\nAttention:\nIf msg.top and top is empty, all msg.topics (msg.topic) will be directly prefixed with 0_userdata.0. . \n\nUpdate 13.09.2022:\nSpaces in topics of objects are no longer replaced with underscores in objects. No differences between all data types.\n\n# Erstellt einen Objektbaum im ioBroker\n\nDiese Node erstellt einen Objektbaum im ioBroker aus einem JAVA Objekt bzw. einem JSON String. \n\nDer Baum wird in jedem Fall unter 0_userdata.0 erstellt und zwar unter dem Topic der inmsg.top
mitgegeben wurde. In dermsg.payload
befindet sich dann der JSON String oder das entsprechende Objekt.\n\nExistierendemsg.topic
Einträge werden gelöscht.\nEin entsprechende iobroker-out Node muss an den Flow angehängt werden. Sie ist nicht Bestandteil des Subflows. In dieser iobroker-out Node darf kein Topic angegeben werden. \n\nFalls msg.top nicht definiert wurde, wird dertop
-Wert der Subflow-Node verwendet.\n\nIn den Eigenschaften der Subflow-Node wurde ein neuer ParameterkeepTopic
hinzugefügt. Standardwert ist false, um das bisherige Verhalten beizubehalten. Setzt man die Eigenschaft auf true, dann wird das originale Topic zwischen dertop
Eigenschaft der Subflow-Node und Eigenschaft des analysierten JSON Objektes eingefügt.\n\nAchtung:\nWenn top und msg.top leer ist, werden alle msg.topics (msg.topic) direkt unter dem Präfix 0_userdata.0., angelegt bzw. ausgegeben. \n\nUpdate 13.09.2022:\nLeerzeichen werden in Topics von Objekten nicht mehr durch Unterstriche ersetzt. Es gibt keine Unterschiede mehr zwischen den Datentypen.",
"category": "",
"in": [
{
"x": 60,
"y": 160,
"wires": [
{
"id": "554b8c663bcb46c2"
}
]
}
],
"out": [
{
"x": 2620,
"y": 280,
"wires": [
{
"id": "0962842ebd23e0d7",
"port": 0
}
]
}
],
"env": [
{
"name": "top",
"type": "str",
"value": "objRoot"
},
{
"name": "keepTopic",
"type": "bool",
"value": "false"
}
],
"meta": {},
"color": "#E2D96E",
"icon": "node-red/batch.svg"
},
{
"id": "3e11e8338f694832",
"type": "split",
"z": "6e802f1553b18149",
"name": "split object",
"splt": "\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "key",
"x": 1370,
"y": 160,
"wires": [
[
"0562a4249c8b856b"
]
]
},
{
"id": "0562a4249c8b856b",
"type": "change",
"z": "6e802f1553b18149",
"name": "add key to topic",
"rules": [
{
"t": "set",
"p": "stateName",
"pt": "msg",
"to": "key",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "topic & '.' & key",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1560,
"y": 160,
"wires": [
[
"ddc90985bef0fafa"
]
]
},
{
"id": "ddc90985bef0fafa",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 1740,
"y": 160,
"wires": [
[
"bfce19b206660fbe"
],
[
"3e11e8338f694832"
],
[
"1a8c03d866b85b12"
]
]
},
{
"id": "bfce19b206660fbe",
"type": "split",
"z": "6e802f1553b18149",
"name": "split array",
"splt": "\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"x": 780,
"y": 280,
"wires": [
[
"e89927810c6d75ec"
]
]
},
{
"id": "e89927810c6d75ec",
"type": "change",
"z": "6e802f1553b18149",
"name": "add index to topic",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "topic & '.' & parts.index",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 970,
"y": 280,
"wires": [
[
"a4d1a5d04564dc77"
]
]
},
{
"id": "f5d52c6a57d08904",
"type": "change",
"z": "6e802f1553b18149",
"name": "finalize msg.topic",
"rules": [
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "'0_userdata.0.' & top",
"tot": "jsonata"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "top & '.' & topic",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2170,
"y": 240,
"wires": [
[
"0962842ebd23e0d7"
]
]
},
{
"id": "c863dd7d651b2272",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "json",
"vt": "json"
},
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 4,
"x": 580,
"y": 160,
"wires": [
[
"158930afddd0780b"
],
[
"3e11e8338f694832"
],
[
"bfce19b206660fbe"
],
[
"a4d1a5d04564dc77"
]
]
},
{
"id": "158930afddd0780b",
"type": "json",
"z": "6e802f1553b18149",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 770,
"y": 120,
"wires": [
[
"3649300b4c233b10"
]
]
},
{
"id": "1a8c03d866b85b12",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is msg.top != null",
"property": "top",
"propertyType": "msg",
"rules": [
{
"t": "nnull"
},
{
"t": "null"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1950,
"y": 280,
"wires": [
[
"f5d52c6a57d08904"
],
[
"74c895ce724750de"
]
]
},
{
"id": "e023fe88445ce43e",
"type": "change",
"z": "6e802f1553b18149",
"name": "",
"rules": [
{
"t": "delete",
"p": "topic",
"pt": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 350,
"y": 200,
"wires": [
[
"c863dd7d651b2272"
]
]
},
{
"id": "3649300b4c233b10",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is array?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 900,
"y": 120,
"wires": [
[
"bfce19b206660fbe"
],
[
"3e11e8338f694832"
]
]
},
{
"id": "1b8480cd2df7ba3f",
"type": "comment",
"z": "6e802f1553b18149",
"name": "Array",
"info": "",
"x": 600,
"y": 280,
"wires": []
},
{
"id": "b3541807672be040",
"type": "comment",
"z": "6e802f1553b18149",
"name": "object",
"info": "",
"x": 1340,
"y": 100,
"wires": []
},
{
"id": "a4d1a5d04564dc77",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 1160,
"y": 280,
"wires": [
[
"3e11e8338f694832"
],
[
"bfce19b206660fbe"
],
[
"1a8c03d866b85b12"
]
]
},
{
"id": "74c895ce724750de",
"type": "change",
"z": "6e802f1553b18149",
"name": "finalize msg.topic",
"rules": [
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "top",
"tot": "env"
},
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "'0_userdata.0.' & top",
"tot": "jsonata"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "top & '.' & topic",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2170,
"y": 320,
"wires": [
[
"0962842ebd23e0d7"
]
]
},
{
"id": "554b8c663bcb46c2",
"type": "switch",
"z": "6e802f1553b18149",
"name": "",
"property": "keepTopic",
"propertyType": "env",
"rules": [
{
"t": "true"
},
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 170,
"y": 160,
"wires": [
[
"e30ba9f0483285e4"
],
[
"e023fe88445ce43e"
]
]
},
{
"id": "e30ba9f0483285e4",
"type": "change",
"z": "6e802f1553b18149",
"name": "",
"rules": [
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "/",
"fromt": "str",
"to": ".",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 350,
"y": 120,
"wires": [
[
"c863dd7d651b2272"
]
]
},
{
"id": "0962842ebd23e0d7",
"type": "change",
"z": "6e802f1553b18149",
"name": "translate invalid chars in topic",
"rules": [
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "..",
"fromt": "str",
"to": ".",
"tot": "str"
},
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "€",
"fromt": "str",
"to": "EUR",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2430,
"y": 280,
"wires": [
[]
]
},
{
"id": "16c33b1e099682e0",
"type": "subflow",
"name": "solar inverter",
"info": "TCP server for LSW3 wifi logger \r\nused with Sofar inverters\r\n\r\n## Supported inverters\r\n\r\nSofar KTL-X \r\nSofar ME3000SP \r\nSofar HYD6000-ES \r\n\r\n### Outputs\r\n1. JSON data\r\n: payload (object) : parsed data object.\r\n: topic (string) : frame data type.\r\n\r\n2. debug\r\n: payload (buffer) : raw data from inverter.\r\n: topic (string) : message origin.\r\n: frameType (string) : frame data type.\r\n\r\n### Details\r\n\r\nDefault local server port: \r\n10000
\r\n\r\nDefault host IP: \r\n47.88.8.200
(data1.solarmanpv.com
) \r\nor \r\n115.29.186.234
(data2.solarmanpv.com
) \r\n\r\nDefault port nr: \r\n10000
\r\n\r\n### References\r\n\r\n - GitHub - the node github repository\r\n",
"category": "",
"in": [],
"out": [
{
"x": 630,
"y": 260,
"wires": [
{
"id": "5d0553bafc1e3bf7",
"port": 0
}
]
},
{
"x": 1150,
"y": 260,
"wires": [
{
"id": "a92085a4d10f12e6",
"port": 0
}
]
}
],
"env": [
{
"name": "localPort",
"type": "num",
"value": "10000",
"ui": {
"icon": "font-awesome/fa-plug",
"label": {
"en-US": "local port"
},
"type": "input",
"opts": {
"types": [
"num",
"env"
]
}
}
},
{
"name": "resend",
"type": "bool",
"value": "false",
"ui": {
"label": {
"en-US": "forward data to remote server"
},
"type": "checkbox"
}
},
{
"name": "remoteHostIp",
"type": "str",
"value": "47.102.152.71",
"ui": {
"icon": "font-awesome/fa-cloud-upload",
"label": {
"en-US": "remote ip"
},
"type": "input",
"opts": {
"types": [
"str",
"env"
]
}
}
},
{
"name": "remoteHostPort",
"type": "num",
"value": "10000",
"ui": {
"icon": "font-awesome/fa-plug",
"label": {
"en-US": "remote port"
},
"type": "input",
"opts": {
"types": [
"num",
"env"
]
}
}
}
],
"meta": {
"version": "1.2.0",
"author": "serek4",
"desc": "sofar inverter with LSW3 wifi logger",
"license": "MIT"
},
"color": "#20c800",
"outputLabels": [
"JSON data",
"debug"
],
"icon": "node-red/bridge-dash.svg"
},
{
"id": "f0ad23cb42522771",
"type": "tcp in",
"z": "16c33b1e099682e0",
"name": "Inverter stick out",
"server": "server",
"host": "",
"port": "${localPort}",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"topic": "inverter",
"trim": false,
"base64": false,
"tls": "",
"x": 140,
"y": 120,
"wires": [
[
"88b9df11c34f72ea",
"a92085a4d10f12e6",
"5d0553bafc1e3bf7",
"cd050871c5e7245c"
]
]
},
{
"id": "164f421ec73701f9",
"type": "tcp request",
"z": "16c33b1e099682e0",
"name": "Solarmanpv.com",
"server": "",
"port": "",
"out": "time",
"ret": "buffer",
"splitc": "1000",
"newline": "",
"trim": false,
"tls": "",
"x": 810,
"y": 160,
"wires": [
[
"a92085a4d10f12e6"
]
]
},
{
"id": "c511e8c8f7ced576",
"type": "function",
"z": "16c33b1e099682e0",
"name": "generate response",
"func": "const request = msg.payload;\n\nconst timestamp = Math.floor(Date.now() / 1000);\nconst timestampStr = timestamp.toString(16);\nconst timestampBuff = Buffer.from(timestamp.toString(16), 'hex');\n\nconst response = Buffer.alloc(23);\nresponse.writeUInt8(0xA5, 0x00); // msg begin\n\nresponse.writeUInt8(0x0A, 0x01); // msg length\nresponse.writeUInt8(0x00, 0x02);\n\nresponse.writeUInt8(0x10, 0x03);\nresponse.writeUInt8(responseType(request.readUInt8(0x04)), 0x04); // match req type\n\nresponse.writeUInt8(request.readUInt8(0x05) >= 0xFF ? 0x00 : request.readUInt8(0x05) + 0x01, 0x05); // get from req + 1\nresponse.writeUInt8(request.readUInt8(0x06), 0x06); // get from req\n\nresponse.writeUInt32LE(request.readUInt32LE(0x07), 0x07); // wifi SN - get from req\n\nresponse.writeUInt8(request.readUInt8(0x0B), 0x0B); // get from req\nresponse.writeUInt8(0x01, 0x0C);\n\nresponse.writeUInt32LE(timestampBuff.readUInt32BE(0x00), 0x0D); // timestamp\n\nresponse.writeUInt8(0x78, 0x11);\nresponse.writeUInt8(0x00, 0x12);\nresponse.writeUInt8(0x00, 0x13);\nresponse.writeUInt8(0x00, 0x14);\n\nresponse.writeUInt8(calculateChecksum(response), 0x15); //checksum\nresponse.writeUInt8(0x15, 0x16); // msg end\n\nmsg.payload = response;\nmsg.topic = 'local_server';\nreturn msg;\n\nfunction responseType(requestTypeByte) {\n // node.warn(req code: 0x${requestTypeByte.toString(16)}
);\n let responseTypeByte = requestTypeByte - (0x30);\n responseTypeByte = Math.min(Math.max(responseTypeByte, 0x00), 0xFF); //min 0x00 max 255\n // switch(requestTypeByte) {\n // case 0x41:\n // responseTypeByte = 0x11;\n // break;\n // case 0x42:\n // responseTypeByte = 0x12;\n // break;\n // case 0x43:\n // responseTypeByte = 0x13;\n // break;\n // case 0x47:\n // responseTypeByte = 0x17;\n // break;\n // case 0x48:\n // responseTypeByte = 0x18;\n // break;\n // default:\n // responseTypeByte = 0xFF;\n // }\n // node.warn(res code: 0x${responseTypeByte.toString(16)}
);\n return responseTypeByte;\n}\nfunction calculateChecksum(message) {\n message = message.slice(1, -2);\n let sum = 0x0;\n for (const key of message.keys()) {\n sum += message[key];\n }\n sum = sum % 256;\n // node.warn( sum.toString(16));\n return sum;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 810,
"y": 120,
"wires": [
[
"95b6eef2f2bbf823",
"a92085a4d10f12e6"
]
],
"inputLabels": [
"data from wifi stick"
],
"outputLabels": [
"response out"
]
},
{
"id": "95b6eef2f2bbf823",
"type": "tcp out",
"z": "16c33b1e099682e0",
"name": "Inverter stick response in",
"host": "",
"port": "10000",
"beserver": "reply",
"base64": false,
"end": true,
"tls": "",
"x": 1090,
"y": 120,
"wires": []
},
{
"id": "5d0553bafc1e3bf7",
"type": "function",
"z": "16c33b1e099682e0",
"name": "decode msg",
"func": "let buffer = msg.payload;\nlet data = {};\nswitch (buffer.length) {\n case 547: // data - BMI-600 & SUN600G3-EU-230\n data.loggerSN = buffer.readUInt32LE(0x07);\n\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x29)).trimEnd();\n \n data.dailyGenerationkWh = buffer.readUInt32LE(0x2C) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x30) / 10;\n\n data.powerGridVoltageV = buffer.readUInt16LE(0x38) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x3E) / 10;\n\n data.ACFrequencyHz = buffer.readUInt16LE(0x44) / 100;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x46);\n data.tempHeatSink = buffer.readInt16LE(0x4A) / 100;\n \n\n data.DCPV1VoltageV = buffer.readUInt16LE(0x60) / 10;\n data.DCPV1CurrentA = buffer.readUInt16LE(0x62) / 10;\n data.DCPV2VoltageV = buffer.readUInt16LE(0x64) / 10;\n data.DCPV2CurrentA = buffer.readUInt16LE(0x66) / 10;\n \n data.ComProtVer = (buffer.toString('utf8', 0x70, 0x77)).trimEnd();\n data.ContrBoardVer = (buffer.toString('utf8', 0x78, 0x7F)).trimEnd();\n data.ComBoardVer = (buffer.toString('utf8', 0x80, 0x87)).trimEnd();\n\n data.RatedPowerW = buffer.readUInt16BE(0x8C) / 10;\n \n data.Modul1SN = (buffer.toString('utf8', 0xAA, 0xB9)).trimEnd();\n data.Modul2SN = (buffer.toString('utf8', 0xBA, 0xC9)).trimEnd();\n data.Modul3SN = (buffer.toString('utf8', 0xCA, 0xD9)).trimEnd();\n data.Modul4SN = (buffer.toString('utf8', 0xDA, 0xE9)).trimEnd();\n \n data.overfreqHz = buffer.readUInt16BE(0xEA) / 100;\n data.overfreqPerc = buffer.readUInt16BE(0xEC);\n data.gridVoltageUpLim = buffer.readUInt16BE(0xF6) / 10 ;\n data.gridVoltageLowLim = buffer.readUInt16BE(0xF8) / 10;\n data.gridFreqUpLim = buffer.readUInt16BE(0xFA) / 100;\n data.gridFreqLowLim = buffer.readUInt16BE(0xFC) / 100;\n data.selfcheckTimeSec = buffer.readUInt16BE(0xFE);\n \n data.year = buffer.readUInt8(0x100);\n data.month = buffer.readUInt8(0x101);\n data.day = buffer.readUInt8(0x102);\n data.hour = buffer.readUInt8(0x103);\n data.minute = buffer.readUInt8(0x104);\n data.second = buffer.readUInt8(0x105); \n \n /*\n data.DCPV2PowerW = buffer.readUInt16LE(0x68) * 10;\n data.ACPVoltageV = buffer.readUInt16LE(0x3C) / 10;\n data.ACCurrentA = buffer.readUInt16LE(0x3E) / 100;\n data.ACFrequencyHz = buffer.readUInt16LE(0x40) / 100;\n data.battPowerW = buffer.readInt16LE(0x42) * 10;\n data.battVoltageV = buffer.readUInt16LE(0x44) / 10;\n data.battCurrentA = buffer.readInt16LE(0x46) / 100;\n data.battSoCPct = buffer.readUInt16LE(0x48);\n data.battTempC = buffer.readUInt16LE(0x4A);\n data.consumptionTotalW = buffer.readUInt16LE(0x4E) * 10;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x52);\n data.emergencyOutputVoltageV = buffer.readUInt16LE(0x54) / 10;\n data.emergencyOutputPowerW = buffer.readUInt16LE(0x56) * 10;\n data.dailyGenerationkWh = buffer.readUInt16LE(0x58) / 100;\n data.dailyOnGridEnergykWh = buffer.readUInt16LE(0x5A) / 100;\n data.dailyEnergyPurchasedkWh = buffer.readUInt16LE(0x5C) / 100;\n data.dailyEnergyUsedkWh = buffer.readUInt16LE(0x5E) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x60);\n data.totalOnGridGenerationkWh = buffer.readUInt32LE(0x64);\n data.totalEnergyPurchasedkWh = buffer.readUInt32LE(0x68);\n data.totalEnergyConsumptionkWh = buffer.readUInt32LE(0x6C);\n data.battDailyEnergyDischargedkWh = buffer.readUInt16LE(0x70) / 100;\n data.battDailyEnergyChargedkWh = buffer.readUInt16LE(0x72) / 100;\n data.battTotalEnergyChargedkWh = buffer.readUInt32LE(0x74);\n data.battTotalEnergyDischargedkWh = buffer.readUInt32LE(0x78);\n data.battTotalChargingDischargingTimesNum = buffer.readUInt16LE(0x80);\n data.busVoltageV = buffer.readUInt16LE(0x82) / 10;\n data.LLCbusVoltageV = buffer.readUInt16LE(0x84) / 10;\n data.buckCurrentA = buffer.readUInt16LE(0x86) / 100;\n data.powerGridVoltageV = buffer.readUInt16LE(0x88) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x8A) / 100;\n data.inverterTempC = buffer.readUInt16LE(0x8E);\n data.radiatorTempC = buffer.readUInt16LE(0x90);\n data.currentDCComponentA = buffer.readInt16LE(0x94);\n data.voltageDCComponentV = buffer.readInt16LE(0x96);\n data.dailyGeneratingHours = buffer.readUInt8(0xA4);\n data.totalGeneratingHours = buffer.readUInt32LE(0xA6);\n data.insulationImpedanceToTheGround = buffer.readUInt16LE(0xAA);\n data.insulationImpedancePVToGround = buffer.readUInt16LE(0xAC);\n data.insulationImpedanceCathodeToGround = buffer.readUInt16LE(0xAE);\n \n data.year = buffer.readUInt8(0xC8);\n data.month = buffer.readUInt8(0xC9);\n data.day = buffer.readUInt8(0xCA);\n data.hour = buffer.readUInt8(0xCB);\n data.minute = buffer.readUInt8(0xCC);\n data.second = buffer.readUInt8(0xCD);\n */\n return {\n payload: data,\n topic: "Solar " + data.loggerSN\n };\n break;\n case 208: // data - HYD6000ES\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.DCPV1VoltageV = buffer.readUInt16LE(0x30) / 10;\n data.DCPV1CurrentA = buffer.readUInt16LE(0x32) / 100;\n data.DCPV1PowerW = buffer.readUInt16LE(0x34) * 10;\n data.DCPV2VoltageV = buffer.readUInt16LE(0x36) / 10;\n data.DCPV2CurrentA = buffer.readUInt16LE(0x38) / 100;\n data.DCPV2PowerW = buffer.readUInt16LE(0x3A) * 10;\n data.ACPVoltageV = buffer.readUInt16LE(0x3C) / 10;\n data.ACCurrentA = buffer.readUInt16LE(0x3E) / 100;\n data.ACFrequencyHz = buffer.readUInt16LE(0x40) / 100;\n data.battPowerW = buffer.readInt16LE(0x42) * 10;\n data.battVoltageV = buffer.readUInt16LE(0x44) / 10;\n data.battCurrentA = buffer.readInt16LE(0x46) / 100;\n data.battSoCPct = buffer.readUInt16LE(0x48);\n data.battTempC = buffer.readUInt16LE(0x4A);\n data.consumptionTotalW = buffer.readUInt16LE(0x4E) * 10;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x52);\n data.emergencyOutputVoltageV = buffer.readUInt16LE(0x54) / 10;\n data.emergencyOutputPowerW = buffer.readUInt16LE(0x56) * 10;\n data.dailyGenerationkWh = buffer.readUInt16LE(0x58) / 100;\n data.dailyOnGridEnergykWh = buffer.readUInt16LE(0x5A) / 100;\n data.dailyEnergyPurchasedkWh = buffer.readUInt16LE(0x5C) / 100;\n data.dailyEnergyUsedkWh = buffer.readUInt16LE(0x5E) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x60);\n data.totalOnGridGenerationkWh = buffer.readUInt32LE(0x64);\n data.totalEnergyPurchasedkWh = buffer.readUInt32LE(0x68);\n data.totalEnergyConsumptionkWh = buffer.readUInt32LE(0x6C);\n data.battDailyEnergyDischargedkWh = buffer.readUInt16LE(0x70) / 100;\n data.battDailyEnergyChargedkWh = buffer.readUInt16LE(0x72) / 100;\n data.battTotalEnergyChargedkWh = buffer.readUInt32LE(0x74);\n data.battTotalEnergyDischargedkWh = buffer.readUInt32LE(0x78);\n data.battTotalChargingDischargingTimesNum = buffer.readUInt16LE(0x80);\n data.busVoltageV = buffer.readUInt16LE(0x82) / 10;\n data.LLCbusVoltageV = buffer.readUInt16LE(0x84) / 10;\n data.buckCurrentA = buffer.readUInt16LE(0x86) / 100;\n data.powerGridVoltageV = buffer.readUInt16LE(0x88) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x8A) / 100;\n data.inverterTempC = buffer.readUInt16LE(0x8E);\n data.radiatorTempC = buffer.readUInt16LE(0x90);\n data.currentDCComponentA = buffer.readInt16LE(0x94);\n data.voltageDCComponentV = buffer.readInt16LE(0x96);\n data.dailyGeneratingHours = buffer.readUInt8(0xA4);\n data.totalGeneratingHours = buffer.readUInt32LE(0xA6);\n data.insulationImpedanceToTheGround = buffer.readUInt16LE(0xAA);\n data.insulationImpedancePVToGround = buffer.readUInt16LE(0xAC);\n data.insulationImpedanceCathodeToGround = buffer.readUInt16LE(0xAE);\n data.year = buffer.readUInt8(0xC8);\n data.month = buffer.readUInt8(0xC9);\n data.day = buffer.readUInt8(0xCA);\n data.hour = buffer.readUInt8(0xCB);\n data.minute = buffer.readUInt8(0xCC);\n data.second = buffer.readUInt8(0xCD);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n case 232: // data - ME3000SP\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.tempLogger = buffer.readInt16LE(0x70) / 100;\n data.tempBattery = buffer.readInt16LE(0x30) / 10;\n data.tempInverter = buffer.readInt16LE(0xA6);\n data.tempHeatSink = buffer.readInt16LE(0xA8);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x0C, 0x0E));\n data.tOperationTime = buffer.readUInt32LE(0x0E);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.VAC1 = buffer.readUInt16LE(0x40) / 10;\n data.VAC2 = buffer.readUInt16LE(0x42) / 10;\n data.VAC3 = buffer.readUInt16LE(0x44) / 10;\n data.frequencyPowerGrid = buffer.readUInt16LE(0x46) / 100;\n data.currentPower = buffer.readUInt32LE(0x48);\n data.inverterStatus = decodeInverterStatus(buffer.readUInt8(0x58));\n data.Vbus = buffer.readUInt16LE(0x72) / 10;\n data.VCPU1 = buffer.readUInt16LE(0x74) / 10;\n data.cathode_groundInsulationImpedance = buffer.readUInt16LE(0x80);\n data.countryCode = buffer.readUInt16LE(0x82);\n data.A_phaseDCdistribution = buffer.readUInt16LE(0x8A);\n data.B_phaseDCdistribution = buffer.readUInt16LE(0x8C);\n data.C_phaseDCdistribution = buffer.readUInt16LE(0x8E);\n data.consumptionDailyEnergyPurchase = buffer.readUInt16LE(0x76);\n data.consumptionDailyEnergyUsed = buffer.readUInt16LE(0x78);\n data.consumptionTotal = buffer.readUInt16LE(0x86);\n data.generationTotal = buffer.readUInt16LE(0x7A);\n data.generationTotalOnGrid = buffer.readUInt16LE(0x7E);\n data.battChargeLevel = buffer.readUInt16LE(0x6A);\n data.battChargeDischargePower = buffer.readInt16LE(0x64) * 10;\n data.battCurrent = buffer.readInt16LE(0x68) / 100;\n data.battDailyGenerationPV = buffer.readUInt32LE(0x4C) / 100;\n data.battDailyEnergyCharged = buffer.readUInt16LE(0xc4) / 100;\n data.battDailyEnergyDischarged = buffer.readUInt16LE(0xC6) / 100;\n data.battGenerationCurrent = buffer.readInt16LE(0xA2) / 100;\n data.battTotalEnergyCharged = buffer.readUInt32LE(0xC8);\n data.battTotalEnergyDischarged = buffer.readUInt32LE(0xCC);\n data.battPower = buffer.readInt16LE(0xc2) * 10;\n data.battVoltage = buffer.readUInt16LE(0x66) / 100;\n\n data.busVoltage = buffer.readUInt16LE(0x90) / 10;\n data.busVoltageLlc = buffer.readUInt16LE(0x92) / 10;\n data.buckCurrent = buffer.readInt16LE(0x94) / 100;\n data.voltageDCComponent = buffer.readInt16LE(0xAE) / 10;\n data.epsOutVoltage = buffer.readInt16LE(0x96) / 10;\n data.productionCurrentR = buffer.readUInt16LE(0x98) / 100;\n data.productionCurrentS = buffer.readUInt16LE(0x9E) / 100;\n data.productionCurrentT = buffer.readUInt16LE(0xA0) / 100;\n data.year = buffer.readUInt8(0xE0);\n data.month = buffer.readUInt8(0xE1);\n data.day = buffer.readUInt8(0xE2);\n data.hour = buffer.readUInt8(0xE3);\n data.minute = buffer.readUInt8(0xE4);\n data.second = buffer.readUInt8(0xE5);\n let dateFormat = '20' + String(data.year).padStart(2, '0') + '-' + String(data.month).padStart(2, '0') + '-' + String(data.day).padStart(2, '0') + 'T' + String(data.hour).padStart(2, '0') + ':' + String(data.minute).padStart(2, '0') + ':' + String(data.second).padStart(2, '0')\n data.timestamp = new Date(dateFormat)\n //node.warn(data);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n\n case 164: // data - KTL-X\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x0C, 0x0E));\n data.tOperationTime = buffer.readUInt32LE(0x0E);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.inverterTemp = buffer.readInt16LE(0x30) / 10;\n data.VDC1 = buffer.readUInt16LE(0x32) / 10;\n data.VDC2 = buffer.readUInt16LE(0x34) / 10;\n data.IDC1 = buffer.readUInt16LE(0x36) / 10;\n data.IDC2 = buffer.readUInt16LE(0x38) / 10;\n data.IAC1 = buffer.readUInt16LE(0x3A) / 10;\n data.IAC2 = buffer.readUInt16LE(0x3C) / 10;\n data.IAC3 = buffer.readUInt16LE(0x3E) / 10;\n data.VAC1 = buffer.readUInt16LE(0x40) / 10;\n data.VAC2 = buffer.readUInt16LE(0x42) / 10;\n data.VAC3 = buffer.readUInt16LE(0x44) / 10;\n data.fAC = buffer.readUInt16LE(0x46) / 100;\n\n data.currentPower = buffer.readUInt32LE(0x48);\n\n data.eToday = buffer.readUInt32LE(0x4C) / 100;\n data.eTotal = buffer.readUInt32LE(0x50) / 10;\n\n data.hTotal = buffer.readUInt32LE(0x54);\n\n data.inverterStatus = decodeInverterStatus(buffer.readUInt8(0x58));\n\n data.loggerTemp = buffer.readInt16LE(0x70);\n data.Vbus = buffer.readUInt16LE(0x72) / 10;\n data.VCPU1 = buffer.readUInt16LE(0x74) / 10;\n data.countdownTime = buffer.readUInt16LE(0x78);\n data.PV1insulationResistance = buffer.readUInt16LE(0x7C);\n data.PV2insulationResistance = buffer.readUInt16LE(0x7E);\n data.cathode_groundInsulationImpedance = buffer.readUInt16LE(0x80);\n data.countryCode = buffer.readUInt16LE(0x82);\n data.A_phaseDCdistribution = buffer.readUInt16LE(0x8A);\n data.B_phaseDCdistribution = buffer.readUInt16LE(0x8C);\n data.C_phaseDCdistribution = buffer.readUInt16LE(0x8E);\n\n data.firmware = buffer.toString('utf8', 0x90, 0x94);\n\n data.year = buffer.readUInt8(0x98);\n data.month = buffer.readUInt8(0x99);\n data.day = buffer.readUInt8(0x9A);\n data.hour = buffer.readUInt8(0x9B);\n data.minute = buffer.readUInt8(0x9C);\n data.second = buffer.readUInt8(0x9D);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n case 99: // hello message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.tOperationTime = buffer.readUInt32LE(0x0C);\n data.uploadingFrequency = buffer.readUInt8(0x18);\n data.dataLoggingFrequency = buffer.readUInt8(0x19);\n data.heartbeatFrequency = buffer.readUInt8(0x1A);\n data.commandType = buffer.readUInt8(0x1B);\n data.signalQuality = buffer.readUInt8(0x1C);\n data.sensorTypeNr = buffer.readUInt8(0x1D);\n data.moduleVersion = buffer.toString('utf8', 0x1E, 0x46);\n data.macAddress = readMacAddress(buffer.slice(0x46, 0x4C));\n data.localIP = buffer.toString('utf8', 0x4C, 0x5B);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x5F, 0x61));\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello'\n };\n break;\n case 41: // hello_cd message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.totalOperationTime = buffer.readUInt32LE(0x0C);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello_cd'\n };\n break;\n case 73: // hello_end message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.totalOperationTime = buffer.readUInt32LE(0x0C);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello_end'\n };\n break;\n\n default:\n // node.warn(buffer.length);\n return null\n break;\n}\n\nfunction readMacAddress(buffer) {\n let macAddressStr = '';\n for (let buff of buffer) {\n // node.warn(buff.toString(16));\n macAddressStr +=${
0${(buff.toString(16))}.slice(-2)}:
;\n }\n return macAddressStr.substring(0, 17);\n}\nfunction readSensorTypeList(buffer) {\n let SensorTypeListStr =${
0${(buffer[1].toString(16))}.slice(-2)}
;\n SensorTypeListStr +=${
0${(buffer[0].toString(16))}.slice(-2)}
;\n return SensorTypeListStr;\n}\n\nfunction decodeInverterStatus(status) {\n switch (status) {\n case 0x00:\n returnstandby
;\n case 0x02:\n returnnormal
;\n case 0x03:\n returnfault
;\n case 0x04:\n returnpermanent
;\n default:\n return0x${
0${(status.toString(16))}.slice(-2)}
;\n }\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 260,
"wires": [
[]
],
"inputLabels": [
"wifi stick buffer in"
],
"outputLabels": [
"decoded message object"
]
},
{
"id": "28fb73099b82097a",
"type": "change",
"z": "16c33b1e099682e0",
"name": "set host and port",
"rules": [
{
"t": "set",
"p": "host",
"pt": "msg",
"to": "remoteHostIp",
"tot": "env"
},
{
"t": "set",
"p": "port",
"pt": "msg",
"to": "remoteHostPort",
"tot": "env"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "remote_server",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 610,
"y": 160,
"wires": [
[
"164f421ec73701f9"
]
]
},
{
"id": "88b9df11c34f72ea",
"type": "switch",
"z": "16c33b1e099682e0",
"name": "",
"property": "resend",
"propertyType": "env",
"rules": [
{
"t": "istype",
"v": "boolean",
"vt": "boolean"
},
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 430,
"y": 120,
"wires": [
[
"c511e8c8f7ced576"
],
[
"28fb73099b82097a"
]
],
"inputLabels": [
"inverter data"
],
"outputLabels": [
"local server only",
"remote server"
]
},
{
"id": "7113cbe45845bb03",
"type": "status",
"z": "16c33b1e099682e0",
"name": "",
"scope": [],
"x": 120,
"y": 40,
"wires": [
[]
]
},
{
"id": "a92085a4d10f12e6",
"type": "function",
"z": "16c33b1e099682e0",
"name": "add msg.frameType",
"func": "let frameType = '';\nswitch (msg.payload.length) {\n case 99:\n frameType = 'hello(99)';\n break;\n case 23:\n frameType = 'srv_res(23)';\n break;\n case 164: // KTL-X\n case 208: // HYD6000ES\n case 232: // ME3000SP\n case 547: // BMI-600 & SUN600G3-EU-230\n frameType =data(${msg.payload.length})
;\n break;\n case 14:\n frameType = 'heartbeat(14)';\n break;\n case 41:\n frameType = 'hello_cd(41)';\n break;\n case 73:\n frameType = 'hello_end(73)';\n break;\n default:\n frameType =unknown(${msg.payload.length})
;\n break;\n}\nmsg.frameType = frameType;\n// delete TCP nodes properties\ndelete msg.ip;\ndelete msg.host;\ndelete msg.port;\ndelete msg._session;\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 960,
"y": 260,
"wires": [
[]
]
},
{
"id": "f5a0b9593db8e99f",
"type": "file in",
"z": "16c33b1e099682e0",
"name": "",
"filename": "/opt/iobroker/test.bin",
"filenameType": "str",
"format": "stream",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 340,
"y": 360,
"wires": [
[]
]
},
{
"id": "d8a0e2dceb7262ee",
"type": "inject",
"z": "16c33b1e099682e0",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 360,
"wires": [
[]
]
},
{
"id": "a3d422ba2bba844c",
"type": "comment",
"z": "16c33b1e099682e0",
"name": "Hinweise zum solar Node",
"info": "https://github.com/serek4/node-red-sofar-inverter\nhttp://192.168.1.90/config_hide.html\nhttp://192.168.1.91/config_hide.html\nhttp://192.168.1.92/config_hide.html\nhttps://github.com/0xb1ff/solarman-dissector\nresent disabled in Eigenschaften",
"x": 610,
"y": 520,
"wires": []
},
{
"id": "cd050871c5e7245c",
"type": "debug",
"z": "16c33b1e099682e0",
"name": "debug 3",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 460,
"y": 40,
"wires": []
},
{
"id": "63d79ea54fec0a3b",
"type": "subflow:16c33b1e099682e0",
"z": "c6d3f0d.558371",
"name": "Solar inverter",
"x": 130,
"y": 500,
"wires": [
[
"f301baaf83223c8e"
],
[
"487a65aaad85458b"
]
]
},
{
"id": "f301baaf83223c8e",
"type": "json",
"z": "c6d3f0d.558371",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 330,
"y": 500,
"wires": [
[
"df544262fde22aca"
]
]
},
{
"id": "255e0e6a6050f427",
"type": "comment",
"z": "c6d3f0d.558371",
"name": "Hinweise zum solar Node",
"info": "https://github.com/serek4/node-red-sofar-inverter\nhttp://192.168.1.90/config_hide.html\nhttp://192.168.1.91/config_hide.html\nhttp://192.168.1.92/config_hide.html\nhttps://github.com/0xb1ff/solarman-dissector",
"x": 130,
"y": 560,
"wires": []
},
{
"id": "df544262fde22aca",
"type": "subflow:6e802f1553b18149",
"z": "c6d3f0d.558371",
"name": "",
"env": [
{
"name": "keepTopic",
"value": "true",
"type": "bool"
}
],
"x": 570,
"y": 500,
"wires": [
[
"5b2b626a187f8453"
]
]
},
{
"id": "5b2b626a187f8453",
"type": "ioBroker out",
"z": "c6d3f0d.558371",
"name": "",
"topic": "",
"ack": "true",
"autoCreate": "true",
"stateName": "",
"role": "",
"payloadType": "",
"readonly": "",
"stateUnit": "",
"stateMin": "",
"stateMax": "",
"x": 820,
"y": 500,
"wires": []
},
{
"id": "487a65aaad85458b",
"type": "debug",
"z": "c6d3f0d.558371",
"name": "debug 2",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 340,
"y": 560,
"wires": []
}
]
-
@painless Super. Ich hab mir gestern frisch eine Node-Red Instanz aufgesetzt. Darin habe ich einen Flow aufgesetzt.
Soweit meine laienhafte Vorstellung nach deinem Post. Bisher kamen nur 3mal 3 Sequenzen raus.
Jetzt les ich mal deinen Ansatz in Ruhe durch und versuchs zu verstehen
-
@rene55
Diese Pakete kommen schon mal vom Bosswerk, da alle Pakete im Data 5 Protokoll mit 165 beginnen. Messages mit einer Länge von 143 sind mir allerdings unbekannt.
Mindest die Heartbeat-Msg mit der Länge von 14 sollte alle 5 Minuten ankommen.Hast Du im config_hide Menü Server A auf deinen node-red umgebogen ? Beim optionalen Server kommt zumindestens bei mir nix an.
Ist der tcp Input-node im Ausgang auf "Strom von" "Buffer" gestellt ?
Nimm meinen Flow, da sparst Du ne Menge Zeit und kannst bei der weiteren Decodierung helfen. Im Flow selbst kannst Du das forwarding der Nachrichten zum Solarman Server ein/ausschalten. Damit funktioniert dann auch noch die Solarman App.
-
@painless Ja richtig, ich hab den CN-Server im Server A rausgeschmissen und meinen ioB dort eingetragen. Ich hatte auch schonmal mit Server B versucht - aber da kam gar nichts. Ist wahrscheinlich Firmware abhängig und hier schlicht 'vergessen' worden.
Nochmals danke für deinen Flow, bei dem ich jedoch beim Import einen Fehler hatte.
Und hier muss ich zugeben, dass ich von NodeRed keine Ahnung habe und den völlig unterschätzt habe. Wo kann ich denn die Seriennummer eintragen. Kannst du mir hierbei kurz helfen? -
@rene55
Stimmt, die Fehlermeldung bekomme ich auch, wenn ich meinen eigenen Flow importieren will. Sorry, da muss beim Export etwas schief gegangen sein.Versuche es bitte damit nochmal:
[
{
"id": "6e802f1553b18149",
"type": "subflow",
"name": "JSON or Obj to IOBroker",
"info": "# Creates an IOBroker tree\n\nThis node creates an IOBroker tree out of an Java-Object or JSON String.\n\nThe object tree will be created under 0_userdata.0\nIn addition to the JSON-String or Java Object asmsg.payload
it is necessary to specify amsg.top
properity in addition to the msg-Object.\n\nThe object tree will be created under 0_userdata.0\n\nExistingmsg.topic
entries will be deleted.\nAn iobroker-out node has to be appended to this subflow node. It is not part of the subflow itself. No topic should be specified in the iobroker out node.\n\nIsmsg.top
property isn't defined, thetop
property of the subflow-node is used. \n\nIn the properties of the subflow node a new propertykeepTopic
has been added. Default is false to keep the current behaviour. If set to true then the originial topic will be placed between thetop
property of the subflow node and the property of the analyzed JSON object.\n\nAttention:\nIf msg.top and top is empty, all msg.topics (msg.topic) will be directly prefixed with 0_userdata.0. . \n\nUpdate 13.09.2022:\nSpaces in topics of objects are no longer replaced with underscores in objects. No differences between all data types.\n\n# Erstellt einen Objektbaum im ioBroker\n\nDiese Node erstellt einen Objektbaum im ioBroker aus einem JAVA Objekt bzw. einem JSON String. \n\nDer Baum wird in jedem Fall unter 0_userdata.0 erstellt und zwar unter dem Topic der inmsg.top
mitgegeben wurde. In dermsg.payload
befindet sich dann der JSON String oder das entsprechende Objekt.\n\nExistierendemsg.topic
Einträge werden gelöscht.\nEin entsprechende iobroker-out Node muss an den Flow angehängt werden. Sie ist nicht Bestandteil des Subflows. In dieser iobroker-out Node darf kein Topic angegeben werden. \n\nFalls msg.top nicht definiert wurde, wird dertop
-Wert der Subflow-Node verwendet.\n\nIn den Eigenschaften der Subflow-Node wurde ein neuer ParameterkeepTopic
hinzugefügt. Standardwert ist false, um das bisherige Verhalten beizubehalten. Setzt man die Eigenschaft auf true, dann wird das originale Topic zwischen dertop
Eigenschaft der Subflow-Node und Eigenschaft des analysierten JSON Objektes eingefügt.\n\nAchtung:\nWenn top und msg.top leer ist, werden alle msg.topics (msg.topic) direkt unter dem Präfix 0_userdata.0., angelegt bzw. ausgegeben. \n\nUpdate 13.09.2022:\nLeerzeichen werden in Topics von Objekten nicht mehr durch Unterstriche ersetzt. Es gibt keine Unterschiede mehr zwischen den Datentypen.",
"category": "",
"in": [
{
"x": 60,
"y": 160,
"wires": [
{
"id": "554b8c663bcb46c2"
}
]
}
],
"out": [
{
"x": 2620,
"y": 280,
"wires": [
{
"id": "0962842ebd23e0d7",
"port": 0
}
]
}
],
"env": [
{
"name": "top",
"type": "str",
"value": "objRoot"
},
{
"name": "keepTopic",
"type": "bool",
"value": "false"
}
],
"meta": {},
"color": "#E2D96E",
"icon": "node-red/batch.svg"
},
{
"id": "3e11e8338f694832",
"type": "split",
"z": "6e802f1553b18149",
"name": "split object",
"splt": "\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "key",
"x": 1370,
"y": 160,
"wires": [
[
"0562a4249c8b856b"
]
]
},
{
"id": "0562a4249c8b856b",
"type": "change",
"z": "6e802f1553b18149",
"name": "add key to topic",
"rules": [
{
"t": "set",
"p": "stateName",
"pt": "msg",
"to": "key",
"tot": "msg"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "topic & '.' & key",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 1560,
"y": 160,
"wires": [
[
"ddc90985bef0fafa"
]
]
},
{
"id": "ddc90985bef0fafa",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 1740,
"y": 160,
"wires": [
[
"bfce19b206660fbe"
],
[
"3e11e8338f694832"
],
[
"1a8c03d866b85b12"
]
]
},
{
"id": "bfce19b206660fbe",
"type": "split",
"z": "6e802f1553b18149",
"name": "split array",
"splt": "\n",
"spltType": "str",
"arraySplt": 1,
"arraySpltType": "len",
"stream": false,
"addname": "",
"x": 780,
"y": 280,
"wires": [
[
"e89927810c6d75ec"
]
]
},
{
"id": "e89927810c6d75ec",
"type": "change",
"z": "6e802f1553b18149",
"name": "add index to topic",
"rules": [
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "topic & '.' & parts.index",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 970,
"y": 280,
"wires": [
[
"a4d1a5d04564dc77"
]
]
},
{
"id": "f5d52c6a57d08904",
"type": "change",
"z": "6e802f1553b18149",
"name": "finalize msg.topic",
"rules": [
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "'0_userdata.0.' & top",
"tot": "jsonata"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "top & '.' & topic",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2170,
"y": 240,
"wires": [
[
"0962842ebd23e0d7"
]
]
},
{
"id": "c863dd7d651b2272",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "json",
"vt": "json"
},
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 4,
"x": 580,
"y": 160,
"wires": [
[
"158930afddd0780b"
],
[
"3e11e8338f694832"
],
[
"bfce19b206660fbe"
],
[
"a4d1a5d04564dc77"
]
]
},
{
"id": "158930afddd0780b",
"type": "json",
"z": "6e802f1553b18149",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 770,
"y": 120,
"wires": [
[
"3649300b4c233b10"
]
]
},
{
"id": "1a8c03d866b85b12",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is msg.top != null",
"property": "top",
"propertyType": "msg",
"rules": [
{
"t": "nnull"
},
{
"t": "null"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 1950,
"y": 280,
"wires": [
[
"f5d52c6a57d08904"
],
[
"74c895ce724750de"
]
]
},
{
"id": "e023fe88445ce43e",
"type": "change",
"z": "6e802f1553b18149",
"name": "",
"rules": [
{
"t": "delete",
"p": "topic",
"pt": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 350,
"y": 200,
"wires": [
[
"c863dd7d651b2272"
]
]
},
{
"id": "3649300b4c233b10",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is array?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 900,
"y": 120,
"wires": [
[
"bfce19b206660fbe"
],
[
"3e11e8338f694832"
]
]
},
{
"id": "1b8480cd2df7ba3f",
"type": "comment",
"z": "6e802f1553b18149",
"name": "Array",
"info": "",
"x": 600,
"y": 280,
"wires": []
},
{
"id": "b3541807672be040",
"type": "comment",
"z": "6e802f1553b18149",
"name": "object",
"info": "",
"x": 1340,
"y": 100,
"wires": []
},
{
"id": "a4d1a5d04564dc77",
"type": "switch",
"z": "6e802f1553b18149",
"name": "is type?",
"property": "payload",
"propertyType": "msg",
"rules": [
{
"t": "istype",
"v": "object",
"vt": "object"
},
{
"t": "istype",
"v": "array",
"vt": "array"
},
{
"t": "else"
}
],
"checkall": "true",
"repair": false,
"outputs": 3,
"x": 1160,
"y": 280,
"wires": [
[
"3e11e8338f694832"
],
[
"bfce19b206660fbe"
],
[
"1a8c03d866b85b12"
]
]
},
{
"id": "74c895ce724750de",
"type": "change",
"z": "6e802f1553b18149",
"name": "finalize msg.topic",
"rules": [
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "top",
"tot": "env"
},
{
"t": "set",
"p": "top",
"pt": "msg",
"to": "'0_userdata.0.' & top",
"tot": "jsonata"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "top & '.' & topic",
"tot": "jsonata"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2170,
"y": 320,
"wires": [
[
"0962842ebd23e0d7"
]
]
},
{
"id": "554b8c663bcb46c2",
"type": "switch",
"z": "6e802f1553b18149",
"name": "",
"property": "keepTopic",
"propertyType": "env",
"rules": [
{
"t": "true"
},
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 170,
"y": 160,
"wires": [
[
"e30ba9f0483285e4"
],
[
"e023fe88445ce43e"
]
]
},
{
"id": "e30ba9f0483285e4",
"type": "change",
"z": "6e802f1553b18149",
"name": "",
"rules": [
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "/",
"fromt": "str",
"to": ".",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 350,
"y": 120,
"wires": [
[
"c863dd7d651b2272"
]
]
},
{
"id": "0962842ebd23e0d7",
"type": "change",
"z": "6e802f1553b18149",
"name": "translate invalid chars in topic",
"rules": [
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "..",
"fromt": "str",
"to": ".",
"tot": "str"
},
{
"t": "change",
"p": "topic",
"pt": "msg",
"from": "€",
"fromt": "str",
"to": "EUR",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 2430,
"y": 280,
"wires": [
[]
]
},
{
"id": "16c33b1e099682e0",
"type": "subflow",
"name": "solar inverter",
"info": "TCP server for LSW3 wifi logger \r\nused with Sofar inverters\r\n\r\n## Supported inverters\r\n\r\nSofar KTL-X \r\nSofar ME3000SP \r\nSofar HYD6000-ES \r\n\r\n### Outputs\r\n1. JSON data\r\n: payload (object) : parsed data object.\r\n: topic (string) : frame data type.\r\n\r\n2. debug\r\n: payload (buffer) : raw data from inverter.\r\n: topic (string) : message origin.\r\n: frameType (string) : frame data type.\r\n\r\n### Details\r\n\r\nDefault local server port: \r\n10000
\r\n\r\nDefault host IP: \r\n47.88.8.200
(data1.solarmanpv.com
) \r\nor \r\n115.29.186.234
(data2.solarmanpv.com
) \r\n\r\nDefault port nr: \r\n10000
\r\n\r\n### References\r\n\r\n - GitHub - the node github repository\r\n",
"category": "",
"in": [],
"out": [
{
"x": 630,
"y": 260,
"wires": [
{
"id": "5d0553bafc1e3bf7",
"port": 0
}
]
},
{
"x": 1150,
"y": 260,
"wires": [
{
"id": "a92085a4d10f12e6",
"port": 0
}
]
}
],
"env": [
{
"name": "localPort",
"type": "num",
"value": "10000",
"ui": {
"icon": "font-awesome/fa-plug",
"label": {
"en-US": "local port"
},
"type": "input",
"opts": {
"types": [
"num",
"env"
]
}
}
},
{
"name": "resend",
"type": "bool",
"value": "false",
"ui": {
"label": {
"en-US": "forward data to remote server"
},
"type": "checkbox"
}
},
{
"name": "remoteHostIp",
"type": "str",
"value": "47.102.152.71",
"ui": {
"icon": "font-awesome/fa-cloud-upload",
"label": {
"en-US": "remote ip"
},
"type": "input",
"opts": {
"types": [
"str",
"env"
]
}
}
},
{
"name": "remoteHostPort",
"type": "num",
"value": "10000",
"ui": {
"icon": "font-awesome/fa-plug",
"label": {
"en-US": "remote port"
},
"type": "input",
"opts": {
"types": [
"num",
"env"
]
}
}
}
],
"meta": {
"version": "1.2.0",
"author": "serek4",
"desc": "sofar inverter with LSW3 wifi logger",
"license": "MIT"
},
"color": "#20c800",
"outputLabels": [
"JSON data",
"debug"
],
"icon": "node-red/bridge-dash.svg"
},
{
"id": "f0ad23cb42522771",
"type": "tcp in",
"z": "16c33b1e099682e0",
"name": "Inverter stick out",
"server": "server",
"host": "",
"port": "${localPort}",
"datamode": "stream",
"datatype": "buffer",
"newline": "",
"topic": "inverter",
"trim": false,
"base64": false,
"tls": "",
"x": 140,
"y": 120,
"wires": [
[
"88b9df11c34f72ea",
"a92085a4d10f12e6",
"5d0553bafc1e3bf7"
]
]
},
{
"id": "164f421ec73701f9",
"type": "tcp request",
"z": "16c33b1e099682e0",
"name": "Solarmanpv.com",
"server": "",
"port": "",
"out": "time",
"ret": "buffer",
"splitc": "1000",
"newline": "",
"trim": false,
"tls": "",
"x": 810,
"y": 160,
"wires": [
[
"a92085a4d10f12e6"
]
]
},
{
"id": "c511e8c8f7ced576",
"type": "function",
"z": "16c33b1e099682e0",
"name": "generate response",
"func": "const request = msg.payload;\n\nconst timestamp = Math.floor(Date.now() / 1000);\nconst timestampStr = timestamp.toString(16);\nconst timestampBuff = Buffer.from(timestamp.toString(16), 'hex');\n\nconst response = Buffer.alloc(23);\nresponse.writeUInt8(0xA5, 0x00); // msg begin\n\nresponse.writeUInt8(0x0A, 0x01); // msg length\nresponse.writeUInt8(0x00, 0x02);\n\nresponse.writeUInt8(0x10, 0x03);\nresponse.writeUInt8(responseType(request.readUInt8(0x04)), 0x04); // match req type\n\nresponse.writeUInt8(request.readUInt8(0x05) >= 0xFF ? 0x00 : request.readUInt8(0x05) + 0x01, 0x05); // get from req + 1\nresponse.writeUInt8(request.readUInt8(0x06), 0x06); // get from req\n\nresponse.writeUInt32LE(request.readUInt32LE(0x07), 0x07); // wifi SN - get from req\n\nresponse.writeUInt8(request.readUInt8(0x0B), 0x0B); // get from req\nresponse.writeUInt8(0x01, 0x0C);\n\nresponse.writeUInt32LE(timestampBuff.readUInt32BE(0x00), 0x0D); // timestamp\n\nresponse.writeUInt8(0x78, 0x11);\nresponse.writeUInt8(0x00, 0x12);\nresponse.writeUInt8(0x00, 0x13);\nresponse.writeUInt8(0x00, 0x14);\n\nresponse.writeUInt8(calculateChecksum(response), 0x15); //checksum\nresponse.writeUInt8(0x15, 0x16); // msg end\n\nmsg.payload = response;\nmsg.topic = 'local_server';\nreturn msg;\n\nfunction responseType(requestTypeByte) {\n // node.warn(req code: 0x${requestTypeByte.toString(16)}
);\n let responseTypeByte = requestTypeByte - (0x30);\n responseTypeByte = Math.min(Math.max(responseTypeByte, 0x00), 0xFF); //min 0x00 max 255\n // switch(requestTypeByte) {\n // case 0x41:\n // responseTypeByte = 0x11;\n // break;\n // case 0x42:\n // responseTypeByte = 0x12;\n // break;\n // case 0x43:\n // responseTypeByte = 0x13;\n // break;\n // case 0x47:\n // responseTypeByte = 0x17;\n // break;\n // case 0x48:\n // responseTypeByte = 0x18;\n // break;\n // default:\n // responseTypeByte = 0xFF;\n // }\n // node.warn(res code: 0x${responseTypeByte.toString(16)}
);\n return responseTypeByte;\n}\nfunction calculateChecksum(message) {\n message = message.slice(1, -2);\n let sum = 0x0;\n for (const key of message.keys()) {\n sum += message[key];\n }\n sum = sum % 256;\n // node.warn( sum.toString(16));\n return sum;\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 810,
"y": 120,
"wires": [
[
"95b6eef2f2bbf823",
"a92085a4d10f12e6"
]
],
"inputLabels": [
"data from wifi stick"
],
"outputLabels": [
"response out"
]
},
{
"id": "95b6eef2f2bbf823",
"type": "tcp out",
"z": "16c33b1e099682e0",
"name": "Inverter stick response in",
"host": "",
"port": "10000",
"beserver": "reply",
"base64": false,
"end": true,
"tls": "",
"x": 1090,
"y": 120,
"wires": []
},
{
"id": "5d0553bafc1e3bf7",
"type": "function",
"z": "16c33b1e099682e0",
"name": "decode msg",
"func": "let buffer = msg.payload;\nlet data = {};\nswitch (buffer.length) {\n case 547: // data - BMI-600 & SUN600G3-EU-230\n data.loggerSN = buffer.readUInt32LE(0x07);\n\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x29)).trimEnd();\n \n data.dailyGenerationkWh = buffer.readUInt32LE(0x2C) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x30) / 10;\n\n data.powerGridVoltageV = buffer.readUInt16LE(0x38) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x3E) / 10;\n\n data.ACFrequencyHz = buffer.readUInt16LE(0x44) / 100;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x46);\n data.tempHeatSink = buffer.readInt16LE(0x4A) / 100;\n \n\n data.DCPV1VoltageV = buffer.readUInt16LE(0x60) / 10;\n data.DCPV1CurrentA = buffer.readUInt16LE(0x62) / 10;\n data.DCPV2VoltageV = buffer.readUInt16LE(0x64) / 10;\n data.DCPV2CurrentA = buffer.readUInt16LE(0x66) / 10;\n \n data.ComProtVer = (buffer.toString('utf8', 0x70, 0x77)).trimEnd();\n data.ContrBoardVer = (buffer.toString('utf8', 0x78, 0x7F)).trimEnd();\n data.ComBoardVer = (buffer.toString('utf8', 0x80, 0x87)).trimEnd();\n\n data.RatedPowerW = buffer.readUInt16BE(0x8C) / 10;\n \n data.Modul1SN = (buffer.toString('utf8', 0xAA, 0xB9)).trimEnd();\n data.Modul2SN = (buffer.toString('utf8', 0xBA, 0xC9)).trimEnd();\n data.Modul3SN = (buffer.toString('utf8', 0xCA, 0xD9)).trimEnd();\n data.Modul4SN = (buffer.toString('utf8', 0xDA, 0xE9)).trimEnd();\n \n data.overfreqHz = buffer.readUInt16BE(0xEA) / 100;\n data.overfreqPerc = buffer.readUInt16BE(0xEC);\n data.gridVoltageUpLim = buffer.readUInt16BE(0xF6) / 10 ;\n data.gridVoltageLowLim = buffer.readUInt16BE(0xF8) / 10;\n data.gridFreqUpLim = buffer.readUInt16BE(0xFA) / 100;\n data.gridFreqLowLim = buffer.readUInt16BE(0xFC) / 100;\n data.selfcheckTimeSec = buffer.readUInt16BE(0xFE);\n \n data.year = buffer.readUInt8(0x100);\n data.month = buffer.readUInt8(0x101);\n data.day = buffer.readUInt8(0x102);\n data.hour = buffer.readUInt8(0x103);\n data.minute = buffer.readUInt8(0x104);\n data.second = buffer.readUInt8(0x105); \n \n /*\n data.DCPV2PowerW = buffer.readUInt16LE(0x68) * 10;\n data.ACPVoltageV = buffer.readUInt16LE(0x3C) / 10;\n data.ACCurrentA = buffer.readUInt16LE(0x3E) / 100;\n data.ACFrequencyHz = buffer.readUInt16LE(0x40) / 100;\n data.battPowerW = buffer.readInt16LE(0x42) * 10;\n data.battVoltageV = buffer.readUInt16LE(0x44) / 10;\n data.battCurrentA = buffer.readInt16LE(0x46) / 100;\n data.battSoCPct = buffer.readUInt16LE(0x48);\n data.battTempC = buffer.readUInt16LE(0x4A);\n data.consumptionTotalW = buffer.readUInt16LE(0x4E) * 10;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x52);\n data.emergencyOutputVoltageV = buffer.readUInt16LE(0x54) / 10;\n data.emergencyOutputPowerW = buffer.readUInt16LE(0x56) * 10;\n data.dailyGenerationkWh = buffer.readUInt16LE(0x58) / 100;\n data.dailyOnGridEnergykWh = buffer.readUInt16LE(0x5A) / 100;\n data.dailyEnergyPurchasedkWh = buffer.readUInt16LE(0x5C) / 100;\n data.dailyEnergyUsedkWh = buffer.readUInt16LE(0x5E) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x60);\n data.totalOnGridGenerationkWh = buffer.readUInt32LE(0x64);\n data.totalEnergyPurchasedkWh = buffer.readUInt32LE(0x68);\n data.totalEnergyConsumptionkWh = buffer.readUInt32LE(0x6C);\n data.battDailyEnergyDischargedkWh = buffer.readUInt16LE(0x70) / 100;\n data.battDailyEnergyChargedkWh = buffer.readUInt16LE(0x72) / 100;\n data.battTotalEnergyChargedkWh = buffer.readUInt32LE(0x74);\n data.battTotalEnergyDischargedkWh = buffer.readUInt32LE(0x78);\n data.battTotalChargingDischargingTimesNum = buffer.readUInt16LE(0x80);\n data.busVoltageV = buffer.readUInt16LE(0x82) / 10;\n data.LLCbusVoltageV = buffer.readUInt16LE(0x84) / 10;\n data.buckCurrentA = buffer.readUInt16LE(0x86) / 100;\n data.powerGridVoltageV = buffer.readUInt16LE(0x88) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x8A) / 100;\n data.inverterTempC = buffer.readUInt16LE(0x8E);\n data.radiatorTempC = buffer.readUInt16LE(0x90);\n data.currentDCComponentA = buffer.readInt16LE(0x94);\n data.voltageDCComponentV = buffer.readInt16LE(0x96);\n data.dailyGeneratingHours = buffer.readUInt8(0xA4);\n data.totalGeneratingHours = buffer.readUInt32LE(0xA6);\n data.insulationImpedanceToTheGround = buffer.readUInt16LE(0xAA);\n data.insulationImpedancePVToGround = buffer.readUInt16LE(0xAC);\n data.insulationImpedanceCathodeToGround = buffer.readUInt16LE(0xAE);\n \n data.year = buffer.readUInt8(0xC8);\n data.month = buffer.readUInt8(0xC9);\n data.day = buffer.readUInt8(0xCA);\n data.hour = buffer.readUInt8(0xCB);\n data.minute = buffer.readUInt8(0xCC);\n data.second = buffer.readUInt8(0xCD);\n */\n return {\n payload: data,\n topic: "Solar " + data.loggerSN\n };\n break;\n case 208: // data - HYD6000ES\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.DCPV1VoltageV = buffer.readUInt16LE(0x30) / 10;\n data.DCPV1CurrentA = buffer.readUInt16LE(0x32) / 100;\n data.DCPV1PowerW = buffer.readUInt16LE(0x34) * 10;\n data.DCPV2VoltageV = buffer.readUInt16LE(0x36) / 10;\n data.DCPV2CurrentA = buffer.readUInt16LE(0x38) / 100;\n data.DCPV2PowerW = buffer.readUInt16LE(0x3A) * 10;\n data.ACPVoltageV = buffer.readUInt16LE(0x3C) / 10;\n data.ACCurrentA = buffer.readUInt16LE(0x3E) / 100;\n data.ACFrequencyHz = buffer.readUInt16LE(0x40) / 100;\n data.battPowerW = buffer.readInt16LE(0x42) * 10;\n data.battVoltageV = buffer.readUInt16LE(0x44) / 10;\n data.battCurrentA = buffer.readInt16LE(0x46) / 100;\n data.battSoCPct = buffer.readUInt16LE(0x48);\n data.battTempC = buffer.readUInt16LE(0x4A);\n data.consumptionTotalW = buffer.readUInt16LE(0x4E) * 10;\n data.ACOutputTotalPowerW = buffer.readUInt16LE(0x52);\n data.emergencyOutputVoltageV = buffer.readUInt16LE(0x54) / 10;\n data.emergencyOutputPowerW = buffer.readUInt16LE(0x56) * 10;\n data.dailyGenerationkWh = buffer.readUInt16LE(0x58) / 100;\n data.dailyOnGridEnergykWh = buffer.readUInt16LE(0x5A) / 100;\n data.dailyEnergyPurchasedkWh = buffer.readUInt16LE(0x5C) / 100;\n data.dailyEnergyUsedkWh = buffer.readUInt16LE(0x5E) / 100;\n data.generationTotalkWh = buffer.readUInt32LE(0x60);\n data.totalOnGridGenerationkWh = buffer.readUInt32LE(0x64);\n data.totalEnergyPurchasedkWh = buffer.readUInt32LE(0x68);\n data.totalEnergyConsumptionkWh = buffer.readUInt32LE(0x6C);\n data.battDailyEnergyDischargedkWh = buffer.readUInt16LE(0x70) / 100;\n data.battDailyEnergyChargedkWh = buffer.readUInt16LE(0x72) / 100;\n data.battTotalEnergyChargedkWh = buffer.readUInt32LE(0x74);\n data.battTotalEnergyDischargedkWh = buffer.readUInt32LE(0x78);\n data.battTotalChargingDischargingTimesNum = buffer.readUInt16LE(0x80);\n data.busVoltageV = buffer.readUInt16LE(0x82) / 10;\n data.LLCbusVoltageV = buffer.readUInt16LE(0x84) / 10;\n data.buckCurrentA = buffer.readUInt16LE(0x86) / 100;\n data.powerGridVoltageV = buffer.readUInt16LE(0x88) / 10;\n data.powerGridCurrentA = buffer.readUInt16LE(0x8A) / 100;\n data.inverterTempC = buffer.readUInt16LE(0x8E);\n data.radiatorTempC = buffer.readUInt16LE(0x90);\n data.currentDCComponentA = buffer.readInt16LE(0x94);\n data.voltageDCComponentV = buffer.readInt16LE(0x96);\n data.dailyGeneratingHours = buffer.readUInt8(0xA4);\n data.totalGeneratingHours = buffer.readUInt32LE(0xA6);\n data.insulationImpedanceToTheGround = buffer.readUInt16LE(0xAA);\n data.insulationImpedancePVToGround = buffer.readUInt16LE(0xAC);\n data.insulationImpedanceCathodeToGround = buffer.readUInt16LE(0xAE);\n data.year = buffer.readUInt8(0xC8);\n data.month = buffer.readUInt8(0xC9);\n data.day = buffer.readUInt8(0xCA);\n data.hour = buffer.readUInt8(0xCB);\n data.minute = buffer.readUInt8(0xCC);\n data.second = buffer.readUInt8(0xCD);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n case 232: // data - ME3000SP\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.tempLogger = buffer.readInt16LE(0x70) / 100;\n data.tempBattery = buffer.readInt16LE(0x30) / 10;\n data.tempInverter = buffer.readInt16LE(0xA6);\n data.tempHeatSink = buffer.readInt16LE(0xA8);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x0C, 0x0E));\n data.tOperationTime = buffer.readUInt32LE(0x0E);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.VAC1 = buffer.readUInt16LE(0x40) / 10;\n data.VAC2 = buffer.readUInt16LE(0x42) / 10;\n data.VAC3 = buffer.readUInt16LE(0x44) / 10;\n data.frequencyPowerGrid = buffer.readUInt16LE(0x46) / 100;\n data.currentPower = buffer.readUInt32LE(0x48);\n data.inverterStatus = decodeInverterStatus(buffer.readUInt8(0x58));\n data.Vbus = buffer.readUInt16LE(0x72) / 10;\n data.VCPU1 = buffer.readUInt16LE(0x74) / 10;\n data.cathode_groundInsulationImpedance = buffer.readUInt16LE(0x80);\n data.countryCode = buffer.readUInt16LE(0x82);\n data.A_phaseDCdistribution = buffer.readUInt16LE(0x8A);\n data.B_phaseDCdistribution = buffer.readUInt16LE(0x8C);\n data.C_phaseDCdistribution = buffer.readUInt16LE(0x8E);\n data.consumptionDailyEnergyPurchase = buffer.readUInt16LE(0x76);\n data.consumptionDailyEnergyUsed = buffer.readUInt16LE(0x78);\n data.consumptionTotal = buffer.readUInt16LE(0x86);\n data.generationTotal = buffer.readUInt16LE(0x7A);\n data.generationTotalOnGrid = buffer.readUInt16LE(0x7E);\n data.battChargeLevel = buffer.readUInt16LE(0x6A);\n data.battChargeDischargePower = buffer.readInt16LE(0x64) * 10;\n data.battCurrent = buffer.readInt16LE(0x68) / 100;\n data.battDailyGenerationPV = buffer.readUInt32LE(0x4C) / 100;\n data.battDailyEnergyCharged = buffer.readUInt16LE(0xc4) / 100;\n data.battDailyEnergyDischarged = buffer.readUInt16LE(0xC6) / 100;\n data.battGenerationCurrent = buffer.readInt16LE(0xA2) / 100;\n data.battTotalEnergyCharged = buffer.readUInt32LE(0xC8);\n data.battTotalEnergyDischarged = buffer.readUInt32LE(0xCC);\n data.battPower = buffer.readInt16LE(0xc2) * 10;\n data.battVoltage = buffer.readUInt16LE(0x66) / 100;\n\n data.busVoltage = buffer.readUInt16LE(0x90) / 10;\n data.busVoltageLlc = buffer.readUInt16LE(0x92) / 10;\n data.buckCurrent = buffer.readInt16LE(0x94) / 100;\n data.voltageDCComponent = buffer.readInt16LE(0xAE) / 10;\n data.epsOutVoltage = buffer.readInt16LE(0x96) / 10;\n data.productionCurrentR = buffer.readUInt16LE(0x98) / 100;\n data.productionCurrentS = buffer.readUInt16LE(0x9E) / 100;\n data.productionCurrentT = buffer.readUInt16LE(0xA0) / 100;\n data.year = buffer.readUInt8(0xE0);\n data.month = buffer.readUInt8(0xE1);\n data.day = buffer.readUInt8(0xE2);\n data.hour = buffer.readUInt8(0xE3);\n data.minute = buffer.readUInt8(0xE4);\n data.second = buffer.readUInt8(0xE5);\n let dateFormat = '20' + String(data.year).padStart(2, '0') + '-' + String(data.month).padStart(2, '0') + '-' + String(data.day).padStart(2, '0') + 'T' + String(data.hour).padStart(2, '0') + ':' + String(data.minute).padStart(2, '0') + ':' + String(data.second).padStart(2, '0')\n data.timestamp = new Date(dateFormat)\n //node.warn(data);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n\n case 164: // data - KTL-X\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x0C, 0x0E));\n data.tOperationTime = buffer.readUInt32LE(0x0E);\n data.inverterSN = (buffer.toString('utf8', 0x20, 0x30)).trimEnd();\n data.inverterTemp = buffer.readInt16LE(0x30) / 10;\n data.VDC1 = buffer.readUInt16LE(0x32) / 10;\n data.VDC2 = buffer.readUInt16LE(0x34) / 10;\n data.IDC1 = buffer.readUInt16LE(0x36) / 10;\n data.IDC2 = buffer.readUInt16LE(0x38) / 10;\n data.IAC1 = buffer.readUInt16LE(0x3A) / 10;\n data.IAC2 = buffer.readUInt16LE(0x3C) / 10;\n data.IAC3 = buffer.readUInt16LE(0x3E) / 10;\n data.VAC1 = buffer.readUInt16LE(0x40) / 10;\n data.VAC2 = buffer.readUInt16LE(0x42) / 10;\n data.VAC3 = buffer.readUInt16LE(0x44) / 10;\n data.fAC = buffer.readUInt16LE(0x46) / 100;\n\n data.currentPower = buffer.readUInt32LE(0x48);\n\n data.eToday = buffer.readUInt32LE(0x4C) / 100;\n data.eTotal = buffer.readUInt32LE(0x50) / 10;\n\n data.hTotal = buffer.readUInt32LE(0x54);\n\n data.inverterStatus = decodeInverterStatus(buffer.readUInt8(0x58));\n\n data.loggerTemp = buffer.readInt16LE(0x70);\n data.Vbus = buffer.readUInt16LE(0x72) / 10;\n data.VCPU1 = buffer.readUInt16LE(0x74) / 10;\n data.countdownTime = buffer.readUInt16LE(0x78);\n data.PV1insulationResistance = buffer.readUInt16LE(0x7C);\n data.PV2insulationResistance = buffer.readUInt16LE(0x7E);\n data.cathode_groundInsulationImpedance = buffer.readUInt16LE(0x80);\n data.countryCode = buffer.readUInt16LE(0x82);\n data.A_phaseDCdistribution = buffer.readUInt16LE(0x8A);\n data.B_phaseDCdistribution = buffer.readUInt16LE(0x8C);\n data.C_phaseDCdistribution = buffer.readUInt16LE(0x8E);\n\n data.firmware = buffer.toString('utf8', 0x90, 0x94);\n\n data.year = buffer.readUInt8(0x98);\n data.month = buffer.readUInt8(0x99);\n data.day = buffer.readUInt8(0x9A);\n data.hour = buffer.readUInt8(0x9B);\n data.minute = buffer.readUInt8(0x9C);\n data.second = buffer.readUInt8(0x9D);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'data'\n };\n break;\n case 99: // hello message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.tOperationTime = buffer.readUInt32LE(0x0C);\n data.uploadingFrequency = buffer.readUInt8(0x18);\n data.dataLoggingFrequency = buffer.readUInt8(0x19);\n data.heartbeatFrequency = buffer.readUInt8(0x1A);\n data.commandType = buffer.readUInt8(0x1B);\n data.signalQuality = buffer.readUInt8(0x1C);\n data.sensorTypeNr = buffer.readUInt8(0x1D);\n data.moduleVersion = buffer.toString('utf8', 0x1E, 0x46);\n data.macAddress = readMacAddress(buffer.slice(0x46, 0x4C));\n data.localIP = buffer.toString('utf8', 0x4C, 0x5B);\n data.sensorTypeList = readSensorTypeList(buffer.slice(0x5F, 0x61));\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello'\n };\n break;\n case 41: // hello_cd message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.totalOperationTime = buffer.readUInt32LE(0x0C);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello_cd'\n };\n break;\n case 73: // hello_end message\n data.loggerSN = buffer.readUInt32LE(0x07);\n data.totalOperationTime = buffer.readUInt32LE(0x0C);\n\n // node.warn(data);\n return {\n payload: data,\n topic: 'hello_end'\n };\n break;\n\n default:\n // node.warn(buffer.length);\n return null\n break;\n}\n\nfunction readMacAddress(buffer) {\n let macAddressStr = '';\n for (let buff of buffer) {\n // node.warn(buff.toString(16));\n macAddressStr +=${
0${(buff.toString(16))}.slice(-2)}:
;\n }\n return macAddressStr.substring(0, 17);\n}\nfunction readSensorTypeList(buffer) {\n let SensorTypeListStr =${
0${(buffer[1].toString(16))}.slice(-2)}
;\n SensorTypeListStr +=${
0${(buffer[0].toString(16))}.slice(-2)}
;\n return SensorTypeListStr;\n}\n\nfunction decodeInverterStatus(status) {\n switch (status) {\n case 0x00:\n returnstandby
;\n case 0x02:\n returnnormal
;\n case 0x03:\n returnfault
;\n case 0x04:\n returnpermanent
;\n default:\n return0x${
0${(status.toString(16))}.slice(-2)}
;\n }\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 450,
"y": 260,
"wires": [
[]
],
"inputLabels": [
"wifi stick buffer in"
],
"outputLabels": [
"decoded message object"
]
},
{
"id": "28fb73099b82097a",
"type": "change",
"z": "16c33b1e099682e0",
"name": "set host and port",
"rules": [
{
"t": "set",
"p": "host",
"pt": "msg",
"to": "remoteHostIp",
"tot": "env"
},
{
"t": "set",
"p": "port",
"pt": "msg",
"to": "remoteHostPort",
"tot": "env"
},
{
"t": "set",
"p": "topic",
"pt": "msg",
"to": "remote_server",
"tot": "str"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 610,
"y": 160,
"wires": [
[
"164f421ec73701f9"
]
]
},
{
"id": "88b9df11c34f72ea",
"type": "switch",
"z": "16c33b1e099682e0",
"name": "",
"property": "resend",
"propertyType": "env",
"rules": [
{
"t": "istype",
"v": "boolean",
"vt": "boolean"
},
{
"t": "true"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 430,
"y": 120,
"wires": [
[
"c511e8c8f7ced576"
],
[
"28fb73099b82097a"
]
],
"inputLabels": [
"inverter data"
],
"outputLabels": [
"local server only",
"remote server"
]
},
{
"id": "7113cbe45845bb03",
"type": "status",
"z": "16c33b1e099682e0",
"name": "",
"scope": [],
"x": 120,
"y": 40,
"wires": [
[]
]
},
{
"id": "a92085a4d10f12e6",
"type": "function",
"z": "16c33b1e099682e0",
"name": "add msg.frameType",
"func": "let frameType = '';\nswitch (msg.payload.length) {\n case 99:\n frameType = 'hello(99)';\n break;\n case 23:\n frameType = 'srv_res(23)';\n break;\n case 164: // KTL-X\n case 208: // HYD6000ES\n case 232: // ME3000SP\n case 547: // BMI-600 & SUN600G3-EU-230\n frameType =data(${msg.payload.length})
;\n break;\n case 14:\n frameType = 'heartbeat(14)';\n break;\n case 41:\n frameType = 'hello_cd(41)';\n break;\n case 73:\n frameType = 'hello_end(73)';\n break;\n default:\n frameType =unknown(${msg.payload.length})
;\n break;\n}\nmsg.frameType = frameType;\n// delete TCP nodes properties\ndelete msg.ip;\ndelete msg.host;\ndelete msg.port;\ndelete msg._session;\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 960,
"y": 260,
"wires": [
[]
]
},
{
"id": "f5a0b9593db8e99f",
"type": "file in",
"z": "16c33b1e099682e0",
"name": "",
"filename": "/opt/iobroker/test.bin",
"filenameType": "str",
"format": "stream",
"chunk": false,
"sendError": false,
"encoding": "none",
"allProps": false,
"x": 340,
"y": 360,
"wires": [
[]
]
},
{
"id": "d8a0e2dceb7262ee",
"type": "inject",
"z": "16c33b1e099682e0",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 120,
"y": 360,
"wires": [
[]
]
},
{
"id": "a3d422ba2bba844c",
"type": "comment",
"z": "16c33b1e099682e0",
"name": "Hinweise zum solar Node",
"info": "https://github.com/serek4/node-red-sofar-inverter\nhttp://192.168.99.90/config_hide.html\nhttp://192.168.99.91/config_hide.html\nhttp://192.168.99.92/config_hide.html\nhttps://github.com/0xb1ff/solarman-dissector\nresent disabled in Eigenschaften",
"x": 170,
"y": 240,
"wires": []
},
{
"id": "63d79ea54fec0a3b",
"type": "subflow:16c33b1e099682e0",
"z": "c6d3f0d.558371",
"name": "Solar inverter",
"x": 130,
"y": 500,
"wires": [
[
"f301baaf83223c8e"
],
[
"378a1d9b2360b7a0"
]
]
},
{
"id": "f301baaf83223c8e",
"type": "json",
"z": "c6d3f0d.558371",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 330,
"y": 500,
"wires": [
[
"df544262fde22aca"
]
]
},
{
"id": "255e0e6a6050f427",
"type": "comment",
"z": "c6d3f0d.558371",
"name": "Hinweise zum solar Node",
"info": "https://github.com/serek4/node-red-sofar-inverter\nhttp://192.168.99.90/config_hide.html\nhttp://192.168.99.91/config_hide.html\nhttp://192.168.99.92/config_hide.html\nhttps://github.com/0xb1ff/solarman-dissector",
"x": 130,
"y": 560,
"wires": []
},
{
"id": "df544262fde22aca",
"type": "subflow:6e802f1553b18149",
"z": "c6d3f0d.558371",
"name": "",
"env": [
{
"name": "keepTopic",
"value": "true",
"type": "bool"
}
],
"x": 570,
"y": 500,
"wires": [
[
"5b2b626a187f8453"
]
]
},
{
"id": "5b2b626a187f8453",
"type": "ioBroker out",
"z": "c6d3f0d.558371",
"name": "",
"topic": "",
"ack": "true",
"autoCreate": "true",
"stateName": "",
"role": "",
"payloadType": "",
"readonly": "",
"stateUnit": "",
"stateMin": "",
"stateMax": "",
"x": 820,
"y": 500,
"wires": []
},
{
"id": "378a1d9b2360b7a0",
"type": "debug",
"z": "c6d3f0d.558371",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 340,
"y": 560,
"wires": []
}
]
-
-
@painless Mercy. Import ging jetzt ohne Fehler. Erste Erfolge:
Nach deinen Recherchen sollten ja (für meinen Bosswerk MI600) die Satzlänge von 547 die richtigen Daten bringen. Im Debug (wie auch gestern bei meinen ersten Gehversuchen) ist die Satzlänge nur 165! Was schonmal richtig ist, ist die 'Device Serial Number'. Demnach müsste ich jetzt versuchen, aus diesem Satz die richtigen Stellen auszubasteln.
-
@rene55
Warte noch ein paar Minuten. Da kommt noch mehr..
Ja, ich hab auch BMI 600.Mach mal Deinen Chat für mich auf. Da tauschen wir ein paar pers. Daten aus um besser zu kommunizieren...
-
-
@marcel-reis Noch nicht so wirklich. Ich muss ja erst mal verstehen, was da so hin und her geschickt wird. Wird wohl noch etwas dauern (vor der nächsten 'Sonnensaison' sollte das funktionieren).
-
@rene55 kann man euch da irgendwie unterstützten? Leider bin ich eher auf der SPS Seite tätig und hab nicht wirklich viel Berührung mit IT. Aber evtl. Kann ich euch ja irgendwie helfen.
-
@marcel-reis Ich werde um Hilfe rufen, wenn ich ein wenig weiter bin und jemanden zum Testen brauche. Bis dahin - Geduld.
-
@rene55 Hallo,
ich habe meinen Deye 600 jetzt in Betrieb genommen und möchte ihn ohne China-Cloud auswerten.
Könnt ihr mal bitte den aktuellen Stand eurer Bemühungen sowie die notwendigen Konfigurationen posten.
Dann können ev. ja mehrere sich die Sache mit ansehen und gemeinsam fertigstellen.
Seid ihr noch bei der Analyse der empfangenen Daten oder bei der Implementierung in IoBroker? Wird man eine extra Hard- oder Software brauchen oder kann der WR die Daten an eine Instanz im IoBroker senden und sie werden dort ausgewertet?
Ich hoffe das Projekt ist noch nicht begraben...
Vielen Dank
Marc -
@vombruch Der Plan dazu ist, dass sich der neue Adapter zwischen den Wechselrichter und die Cloud klemmt und die Daten dann hier abgreift. Nach den derzeitigen Erkenntnissen wird keine extra Hardware nötig sein. Ich bin aber noch in den ersten Gehversuchen und noch lange nicht soweit, hier schon etwas präsentieren zu können. Zugegeben, das Projekt ist derzeit etwas eingeschlafen (ist ja auch wenig Sonne ).
-
@rene55 Macht es ev. Sinn die Daten vom WR abzurufen wie hier beschrieben:
https://github.com/kbialek/deye-inverter-mqtt
Ich möchte die China-Cloud ganz aus dem WR verbannen und entweder versuchen über Port 8899 abzurufen oder die Daten die er an den Server sendet auszuwerten.
Und nach Möglichkeit sollte das natürlich im IoBroker erfolgen... vielleicht finden sich ja noch ein paar findige Köpfe mit Ideen (;-) -
@vombruch Danke für die zusätzliche Info, ich werd mir das mal anschauen. Ob das mit den Registern auch so bei den Bosswerks klappt, muss sich dann noch rausstellen. Die sollen ja auch aus dem Hause Deye sein.
-
@rene55 Hallo Rene,
ich hab mit dem Docker-Job 2 Tage alle Daten per MQTT. Am 3. Tag ist der Docker leider abgestürzt, ließ sich danach aber auch einfach wieder starten. Es läuft komplett auf dem Raspi.
Denke als Dauerlösung sollte es eine eigene Instanz im IoBroker werden - das wird stabiler laufen als irgendein Background Job. Die Daten werden über Port 8899 abgerufen.
Wenn jemand Ahnung von IoBroker Instanzen hat können wir es gerne umsetzen.
Danke
Marc -
@vombruch Hallo Marc, ich hab das Script auch mal ein paar Stunden laufen lassen. Bei mir kamen da nur sehr wenige Werte z.B. day_energy oder dv1. Zur Zeit haben wir ja nicht das ideale Sonnenwetter, so dass der Wechselrichter oft abschaltet. Wie Anfangs erwähnt hab ich einen Bosswerk MI600. Du hast einen Deye 600 und davon kommen alle relevanten Daten im MQTT an?
-
@vombruch sagte in Bosswerk MI600 Adapter:
Denke als Dauerlösung sollte es eine eigene Instanz im IoBroker werden
Hi. Hast du mal den Modbus-Adapter versucht? Dein Docker hat ja im Prinzip die Werte über Modbus vom Inverter abgeholt und in MQTT wieder eingespielt. Das sollte auch direkt mit dem Modbus Adapter klappen.
Gruß Lars