Sofar Solar inwerter - odczyt danych bezpośrednio przez fabryczny DataLogger

Jutro coś podeśle, dopasowane do tego doc. Ale cały czas nie masz prawidłowo działającej strony statusowej więc słabo to widzę. Jesli to co otrzymałeś rozbiega cie z prawdą to nawet po modbus mogą byc problemy.

Poprosiłem zeby podesłali jeszcze inne dokumentację jeśli mają.
Co do statusu to w zrzutach ekranu na

Też nie ma statusów z falownika. Tak samo u mnie to wyglada

Tylko produkcja tam wygląda sensownie.

Dla porównania
Wszystkie napisane i testowane integracje opierają się na takiej adresacji.

W/g dokumentacji, którą załączyłeś w 2020r. powstała chyba jakaś nowa wersja softu - porównaj adresy i odpowiednie pola.


itd… mapa jest całkiem inna.

Do testów możesz spróbować edytować plik integracji by stefan
https://github.com/StephanJoubert/home_assistant_solarman/blob/main/custom_components/solarman/sofar_lsw3.yaml
Dostosowując rejestry do nowej dokumentacji. Nie musisz zmieniać wszystkich tylko możesz na początek zawęzić się do kilku, pamiętając również zmienić ted dwa odczytywane “banki” rejestrów

requests:
  - start: 0x0000
    end:  0x0027
  - start: 0x0105
    end: 0x0114

lub
Uruchomić mój załacznik i pokazać do wyrzuci debug.

[{"id":"916e4fd2.13596","type":"subflow","name":"Sofar inverter","info":"","category":"","in":[{"x":80,"y":80,"wires":[{"id":"e811032c.4d431"}]}],"out":[{"x":1140,"y":140,"wires":[{"id":"4f9bce54.66b91","port":0}]},{"x":1140,"y":200,"wires":[{"id":"ad8991f0.c505c","port":0}]}],"env":[{"name":"IP","type":"str","value":"","ui":{"type":"input","opts":{"types":["str"]}}},{"name":"serial_nr","type":"num","value":"","ui":{"label":{"en-US":"Logger S\\N"},"type":"input","opts":{"types":["num"]}}},{"name":"s_reg","type":"num","value":"0","ui":{"label":{"en-US":"From Register (dec)"},"type":"input","opts":{"types":["num"]}}},{"name":"e_reg","type":"num","value":"39","ui":{"label":{"en-US":"To Register (dec)"},"type":"input","opts":{"types":["num"]}}}],"meta":{},"color":"#FFAAAA","inputLabels":["in"],"outputLabels":["data","status byte"],"icon":"node-red/status.svg","status":{"x":1140,"y":260,"wires":[{"id":"ad8991f0.c505c","port":0}]}},{"id":"c43f4230.d1f43","type":"function","z":"916e4fd2.13596","name":"CRC16-modbus","func":"var CRCMaster = {\n    StringToCheck: \"\",\n    CleanedString: \"\",\n    CRCTableDNP: [],\n    init: function() {\n        this.CRCDNPInit();\n    },\n    CleanString: function(inputType) {\n        if (inputType == \"ASCII\") {\n            this.CleanedString = this.StringToCheck;\n        } else {\n            if (this.StringToCheck.match(/^[0-9A-F \\t]+$/gi) !== null) {\n                this.CleanedString = this._hexStringToString(this.StringToCheck.toUpperCase().replace(/[\\t ]/g, ''));\n            } else {\n                window.alert(\"String doesn't seem to be a valid Hex input.\");\n                return false;\n            }\n        }\n        return true;\n    },\n    CRCDNPInit: function() {\n        var i, j, crc, c;\n        for (i = 0; i < 256; i++) {\n            crc = 0;\n            c = i;\n            for (j = 0; j < 8; j++) {\n                if ((crc ^ c) & 0x0001) crc = (crc >> 1) ^ 0xA6BC;\n                else crc = crc >> 1;\n                c = c >> 1;\n            }\n            this.CRCTableDNP[i] = crc;\n        }\n    },\n    CRC16Modbus: function() {\n        var crc = 0xFFFF;\n        var str = this.CleanedString;\n        for (var pos = 0; pos < str.length; pos++) {\n            crc ^= str.charCodeAt(pos);\n            for (var i = 8; i !== 0; i--) {\n                if ((crc & 0x0001) !== 0) {\n                    crc >>= 1;\n                    crc ^= 0xA001;\n                } else\n                    crc >>= 1;\n            }\n        }\n        return crc;\n    },\n    _stringToBytes: function(str) {\n        var ch, st, re = [];\n        for (var i = 0; i < str.length; i++) {\n            ch = str.charCodeAt(i); // get char\n            st = []; // set up \"stack\"\n            do {\n                st.push(ch & 0xFF); // push byte to stack\n                ch = ch >> 8; // shift value down by 1 byte\n            }\n            while (ch);\n            // add stack contents to result\n            // done because chars have \"wrong\" endianness\n            re = re.concat(st.reverse());\n        }\n        // return an array of bytes\n        return re;\n    },\n    _hexStringToString: function(inputstr) {\n        var hex = inputstr.toString(); //force conversion\n        var str = '';\n        for (var i = 0; i < hex.length; i += 2)\n            str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));\n        return str;\n    },\n    Calculate: function(str, inputType) {\n        this.StringToCheck = str;\n        if (this.CleanString(inputType)) {\n            crcinputcrc16modbus=this.CRC16Modbus().toString(16).toUpperCase();\n            crcinputcrc16modbus=crcinputcrc16modbus.substr(2) + crcinputcrc16modbus.substr(0, 2); //swap bytes\n   \n        }\n    }\n};\n\nCRCMaster.init();\n\nvar inputType = \"HEX\";\nvar crcinputcrc16modbus;\nvar crcinput = msg.payload;\ncrcinput = crcinput.slice(-12);\nCRCMaster.Calculate(crcinput, inputType);\n\nmsg.payload = msg.payload + crcinputcrc16modbus.toLowerCase();\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":80,"wires":[["c648833b.f8547"]]},{"id":"e811032c.4d431","type":"function","z":"916e4fd2.13596","name":"Out_Frame","func":"var a_start = env.get(\"s_reg\"); //adres startowy rejestru\nvar a_end = env.get(\"e_reg\");   //adres końcowy bloku rejestów\nvar oFrame = \"a5170010450000\"; //start bytes \n\nSN = env.get(\"serial_nr\");\nvar hexSN  = SN.toString(16).toLowerCase();\n//seria No.\noFrame +=(hexSN[6]+hexSN[7]+hexSN[4]+hexSN[5]+hexSN[2]+hexSN[3]+hexSN[0]+hexSN[1]);\n//data field\noFrame +=\"020000000000000000000000000000\"\nvar businessfield=\"0103\"+('0000'+a_start.toString(16).toLowerCase()).slice(-4)+('0000'+(a_end-a_start+1).toString(16).toLowerCase()).slice(-4) ;\noFrame += businessfield;\nmsg.payload = oFrame;\nmsg.host = env.get(\"IP\");\nmsg.port = 8899;\nreturn msg;","outputs":1,"noerr":5,"initialize":"","finalize":"","libs":[],"x":190,"y":80,"wires":[["c43f4230.d1f43"]]},{"id":"c648833b.f8547","type":"function","z":"916e4fd2.13596","name":"CRC frame","func":"var l = parseInt(msg.payload.length)/2;\nvar bFrame =[l+2]; \nvar crc = 0;\nbFrame[0] = parseInt((msg.payload[0]+msg.payload[1]),16); \nfor (i=1; i<l; i++){\n   bFrame[i] = parseInt((msg.payload[i*2]+msg.payload[(i*2)+1]),16);  \n   crc +=bFrame[i];\n   crc &= 255; \n}\nbFrame[i] = crc;\nbFrame[i+1] = 0x15;\nmsg.payload = Buffer.from(bFrame);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":80,"wires":[["82c505a3.984848"]]},{"id":"82c505a3.984848","type":"tcp request","z":"916e4fd2.13596","name":"Sofar","server":"","port":"","out":"time","ret":"buffer","splitc":"100","newline":"","tls":"","x":170,"y":140,"wires":[["4b49d7fe.e74338"]]},{"id":"4f9bce54.66b91","type":"function","z":"916e4fd2.13596","name":"Response To Data ","func":"var l=2*(env.get(\"e_reg\")-env.get(\"s_reg\"))+6;\nmsg.payload = msg.payload.slice(-l,-4)\nreturn msg;","outputs":1,"noerr":2,"initialize":"","finalize":"","libs":[],"x":890,"y":140,"wires":[[]]},{"id":"4b49d7fe.e74338","type":"switch","z":"916e4fd2.13596","name":"","property":"payload[0]","propertyType":"msg","rules":[{"t":"eq","v":"0xA5","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":290,"y":140,"wires":[["11f501f17764f76f"],["ad8991f0.c505c"]]},{"id":"ad8991f0.c505c","type":"change","z":"916e4fd2.13596","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"[0,9]","tot":"bin"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":240,"wires":[[]]},{"id":"11f501f17764f76f","type":"function","z":"916e4fd2.13596","name":"check length","func":"var l = parseInt(msg.payload.length)-13;\nmsg.length = l;\n//return [msg,null];\nif (msg.payload[1] == l){\n  return [msg,null];\n}\nelse {\n    msg.err = \"Length err\"\n    return [null,msg];\n}","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":140,"wires":[["6c16a8123255f98d"],["ad8991f0.c505c"]],"outputLabels":["OK","Error"]},{"id":"6c16a8123255f98d","type":"function","z":"916e4fd2.13596","name":"Check CRC","func":"var l = parseInt(msg.payload.length)-2;\nvar crc = 0;\nfor (i=1; i<l; i++){\n    crc +=msg.payload[i];\n    crc &= 255; \n}\nmsg.crc = crc;\nmsg.crc16 = parseInt((msg.payload[i-2]+msg.payload[i-1]),16); \nif (crc == msg.payload[i]){\n  return [msg,null];\n}\nelse {\n    msg.err = \"CRC err\"\n    return [null,msg];\n}\n","outputs":2,"noerr":7,"initialize":"","finalize":"","libs":[],"x":690,"y":140,"wires":[["4f9bce54.66b91"],["ad8991f0.c505c"]],"outputLabels":["OK","Err"]},{"id":"323664626e5225fb","type":"inject","z":"f51d87293b3e5cc3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"30","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":270,"y":460,"wires":[["7e31e5133b13a46a"]]},{"id":"7e31e5133b13a46a","type":"subflow:916e4fd2.13596","z":"f51d87293b3e5cc3","name":"","env":[{"name":"s_reg","value":"1152","type":"num"},{"name":"e_reg","value":"1213","type":"num"}],"x":480,"y":460,"wires":[["bb37a5e5dcc3f6cd"],["cf3ffd9f5fffd167"]]},{"id":"bb37a5e5dcc3f6cd","type":"debug","z":"f51d87293b3e5cc3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":440,"wires":[]},{"id":"cf3ffd9f5fffd167","type":"debug","z":"f51d87293b3e5cc3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":480,"wires":[]}]

Jeśli masz tą wersję to czeka Cię grubsza robota aby to ręcznie dostosować nawet po modbusie.
Nikt jeszcze nie zrobił tej wersji więc będziesz pionierem
Niestety nie mogę Ci inaczej pomóc , bo nie mam poligonu.

Rozumiem.
Dziękuję jutro się pobawię a jeśli mi się nie uda to zostaje zostać przy chmurze :slight_smile:
Dziękuję za poświęcony czas

@paku2020 … uparłem się i nie odpuszczam
Powróćmy jeszcze raz do metody z regułą na mikrotiku.
Włącz tą regułę i przekieruj cały ruch na IP NR i port 10000
Skopiuj te 3 nody i uruchom.

[{"id":"854cd0c2b509654b","type":"tcp in","z":"41b46ab9.643494","name":"Inverter stick out","server":"server","host":"","port":"10000","datamode":"stream","datatype":"buffer","newline":"","topic":"","base64":false,"tls":"","x":420,"y":420,"wires":[["933cba5f353ba16b","763916ddd9ec5b3b"]]},{"id":"933cba5f353ba16b","type":"function","z":"41b46ab9.643494","name":"decode msg","func":"let buffer = msg.payload;\nlet data = {};\nswitch (buffer.length) {\n    case 146:\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.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.PV2insulationRsistance = buffer.readUInt16LE(0x7E);\n        data.catod_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 {payload: data};\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.moduleVrsion = 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 {payload: data};\n        break;\n    case 41:   // 41bytes message\n        data.loggerSN = buffer.readUInt32LE(0x07);\n        data.totalOperationTime = buffer.readUInt32LE(0x0C);\n        \n        // node.warn(data);\n        return {payload: data};\n        break;\n    case 73:   // 73bytes message\n        data.loggerSN = buffer.readUInt32LE(0x07);\n        data.totalOperationTime = buffer.readUInt32LE(0x0C);\n        \n        // node.warn(data);\n        return {payload: data};\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","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":420,"wires":[["763916ddd9ec5b3b"]],"inputLabels":["wifi stick buffer in"],"outputLabels":["decoded message object"]},{"id":"763916ddd9ec5b3b","type":"debug","z":"41b46ab9.643494","name":"decoded message object","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":480,"wires":[]}]

Przechwyciłem i zdekodowałem cała ramkę, którą wysyła do chmury.
Co 5min powinneś otrzymać 2 msg

sofar ramka

Interesuje mnie długość buffer - to co jest w czerwonym kółku, oraz czy po kliknięciu trzech kropek otrzymasz coś takiego

Oki już działam.
zaraz odpale wszystko i będe aktualizował jak tylko coś wejdzie

edit
po 10min nie ma nic. To chyba musi potrwać chwilę zanim mt będzie przechwytywał dane. Wtedy to chyba po jakimś czasie było. HA pobrał dane z chmury więc poczekam jeszcze

Masz tu coś w MT ip>firewall?

Poki co nic. Ale wtedy kiedyś też nie było. Zapomniałem o tym i gdzieś się pojawiło

Pokaż kartę ip>firewall>NAT>rule zakładka general i action pozostałe nie wypełniasz.

general


action

ip 123 to logger, a ip 80 to ha z nd

A w torch ruch i tak idzie do chmur

Kurcze… mam tak samo !?
To jest głowny router, gdzie dodałeś tą regułę?

Tak to jest router brzegowy.
Może jakiś port po stronie ip loggera?

Nie wiadomo jaki port, ta reguła powinna przekierować wszystko? …spróbuj 80
Może reboot MT?

zrobiłem reboot mt.
Jakieś pakiety skoczyły
bede pisał za niedlugo bo musze wyjść

edit

U mnie całkowicie co innego niż u ciebie

840 …grubo i to nie jest dobra wiadomość :face_with_raised_eyebrow:
zmień w funkcji 146 na 840
funk

Teraz to wygląda tak

Obserwuj długość buffer[xxx] i taką samą wpisz w case.
czy ta liczba xxx się powtarza?
tylko wtedy gdy buffer[xxx] = case xxx, jeśli nie ma takiej regularności to coś przerobimy.

Przeważnie jest 143. Podmieniłem w case, jest jakis błąd

jest już lepiej
zakomentuj te linijki

 //data.A_phaseDCdistribution = buffer.readUInt16LE(0x8A);
        //data.B_phaseDCdistribution = buffer.readUInt16LE(0x8C);
        //data.C_phaseDCdistribution = buffer.readUInt16LE(0x8E);

taki efekt