samil-power-inverter-data-downloader

Samil Power Inverter Data Downloader

A flow to download model- and operating-data from Solarlake Samil Power Inverters.

How does it work?

  1. A TCP listener is started on port 60001. The TCP listener waits for connections from Samil Power devices.
  2. A UDP advertisement package is sent to the broadcast address of the network.
  3. Samil Power device makes a TCP-connection to port 60001.
  4. "TCP Listener status" reports a new connection and we can make the data_request.
  5. "TCP Listener" receives the 1. response, we then parse the data-response and initiate the model_request.
  6. "TCP Listener" receives the 2. response and we parse the model-response.

How to get started

  • Before running the flow for the first time you have to set the broadcast-address to that of your network.

Tested on

Since I only have access to a Samil Power 10000TL-PM that's the only device I could test it with.

References

The functionality of this flow is based on the previous work by semonet. Particularly the file samil.py.

https://github.com/semonet/solarhttps://github.com/semonet/solar

[{"id":"34062c9b.ab3c44","type":"tab","label":"samil-power-inverter","disabled":false,"info":""},{"id":"c551d72e.57d17","type":"tcp in","z":"34062c9b.ab3c44","name":"TCP listener","server":"server","host":"","port":"60001","datamode":"stream","datatype":"buffer","newline":"","topic":"","base64":false,"x":130,"y":280,"wires":[["8a0835d2.335a48"]]},{"id":"a19633a.247865","type":"debug","z":"34062c9b.ab3c44","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":330,"y":80,"wires":[]},{"id":"8488bafb.789af","type":"catch","z":"34062c9b.ab3c44","name":"","scope":null,"uncaught":false,"x":120,"y":80,"wires":[["a19633a.247865"]]},{"id":"b427f914.680258","type":"udp out","z":"34062c9b.ab3c44","name":"","addr":"192.168.0.255","iface":"","port":"60000","ipv":"udp4","outport":"","base64":false,"multicast":"broad","x":610,"y":180,"wires":[]},{"id":"8a0835d2.335a48","type":"function","z":"34062c9b.ab3c44","name":"Set request_type","func":"\nfunction bufFromBuffer (buf, start, end) {\n    var rv = Buffer.alloc(end-start);\n    buf.copy( rv, 0, start, end );\n    return rv;\n}\n\nfunction intFromBuffer (buf, start, end) {\n    return bufFromBuffer(buf, start, end).readUIntBE(0,end-start);\n}\n\nvar request_type = intFromBuffer(msg.payload, 3, 4);\n//node.warn(request_type);\nif (request_type === 139) {\n    // model_request \n    msg.request_type = 'model_request';\n    return [ msg, null ];\n} else if (request_type === 91) {\n    // data_request\n    msg.request_type = 'data_request';\n    return [ null, msg ];\n} else {\n    node.warn(\"Unknown request: \" + msg.payload);\n    return [ null, null ];\n}\n","outputs":2,"noerr":0,"initialize":"","finalize":"","x":370,"y":280,"wires":[["9f265c62.e8ad98"],["17fd9890.cee337"]],"outputLabels":["model_request","data_request"]},{"id":"cbc7a8d5.1339a8","type":"inject","z":"34062c9b.ab3c44","name":"Send advertisement","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"reset","payloadType":"str","x":150,"y":180,"wires":[["ef6eb70.c2edf48"]]},{"id":"ef6eb70.c2edf48","type":"function","z":"34062c9b.ab3c44","name":"advertisement","func":"msg.payload = Buffer.from([85,170,0,12,1,0,0,0,0,7,0,0,0,0,1,19]);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":360,"y":180,"wires":[["b427f914.680258"]]},{"id":"7b3f455.9f817bc","type":"status","z":"34062c9b.ab3c44","name":"TCP listener status","scope":["c551d72e.57d17"],"x":150,"y":440,"wires":[["2eda91da.f9fa8e","be1199ed.420838"]]},{"id":"2eda91da.f9fa8e","type":"switch","z":"34062c9b.ab3c44","name":"status: connect","property":"status.event","propertyType":"msg","rules":[{"t":"eq","v":"connect","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":360,"y":480,"wires":[["cd140e90.39a128"]]},{"id":"654b5c26.e9ddf4","type":"function","z":"34062c9b.ab3c44","name":"model_request","func":"msg.payload = Buffer.from([85,170,0,17,1,0,0,0,0,0,0,0,2,128,1,0,0,0,122,2,14]);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":840,"y":400,"wires":[["714ce327.f1deec"]]},{"id":"714ce327.f1deec","type":"tcp out","z":"34062c9b.ab3c44","host":"","port":"","beserver":"reply","base64":false,"end":false,"name":"TCP send request","x":1070,"y":480,"wires":[]},{"id":"17fd9890.cee337","type":"function","z":"34062c9b.ab3c44","name":"parse data_request","func":"\nfunction bufFromBuffer (buf, start, end) {\n    var rv = Buffer.alloc(end-start);\n    buf.copy( rv, 0, start, end );\n    return rv;\n}\n\nfunction intFromBuffer (buf, start, end) {\n    return bufFromBuffer(buf, start, end).readUIntBE(0,end-start);\n}\n\nfunction stringFromBuffer (buf, start, end) {\n    return bufFromBuffer(buf, start, end).toString('utf-8');\n}\n\nif (Buffer.isBuffer(msg.payload)) {\n    var data = msg.payload;\n    //var response_header = Buffer.alloc(15);\n    //data.copy(response_header,0,2,17);\n    //msg.response_header = bufFromBuffer(data, 2, 17);\n    //var response_payload_size = intFromBuffer(data, 18, 19);\n    var response_payload = bufFromBuffer(data, 19, data.length-2);\n    var op_modes = {0: 'wait', 1: 'normal', 5: 'pv_power_off'};\n    var op_mode;\n    try {\n        op_mode = op_modes[intFromBuffer(response_payload, 48, 50)];\n    } catch (e) {\n        op_mode = stringFromBuffer(response_payload, 48, 50);\n    }\n    msg.result = {\n        'ambiant_temp': intFromBuffer(response_payload, 0, 2) / 10.0,\n        'inverter_temp': intFromBuffer(response_payload, 14, 16) / 10.0,\n        'pv1_voltage': intFromBuffer(response_payload, 2, 4) / 10.0,\n        'pv2_voltage': intFromBuffer(response_payload, 4, 6) / 10.0, \n        'pv1_current': intFromBuffer(response_payload, 6, 8) / 10.0,\n        'pv2_current': intFromBuffer(response_payload, 8, 10) / 10.0, \n        'total_operation_hours': intFromBuffer(response_payload, 38, 42), \n        'operating_mode': op_mode,\n        'energy_today': intFromBuffer(response_payload, 42, 44) / 100.0,\n        'grid_current': intFromBuffer(response_payload, 22, 24) / 10.0,\n        'grid_voltage': intFromBuffer(response_payload, 18, 20) / 10.0,\n        'grid_frequency': intFromBuffer(response_payload, 20, 22) / 100.0,\n        'output_power': intFromBuffer(response_payload, 44, 48),\n        'energy_total': intFromBuffer(response_payload, 34, 38) / 10.0,\n    }\n    msg.payload = [msg.result];\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":590,"y":320,"wires":[["654b5c26.e9ddf4","ec2882d3.382388"]]},{"id":"cd140e90.39a128","type":"function","z":"34062c9b.ab3c44","name":"data_request","func":"msg.payload = Buffer.from([85,170,0,17,1,0,0,0,0,0,0,3,2,128,1,3,232,0,74,2,204]);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":830,"y":480,"wires":[["714ce327.f1deec"]]},{"id":"be1199ed.420838","type":"switch","z":"34062c9b.ab3c44","name":"status: disconnect","property":"status.event","propertyType":"msg","rules":[{"t":"eq","v":"disconnect","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":370,"y":400,"wires":[["88c4283f.820488"]]},{"id":"88c4283f.820488","type":"debug","z":"34062c9b.ab3c44","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"status","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":400,"wires":[]},{"id":"9f265c62.e8ad98","type":"function","z":"34062c9b.ab3c44","name":"parse model_request","func":"\nfunction bufFromBuffer (buf, start, end) {\n    var rv = Buffer.alloc(end-start);\n    buf.copy( rv, 0, start, end );\n    return rv;\n}\n\nfunction stringFromBuffer (buf, start, end) {\n    return bufFromBuffer(buf, start, end).toString('utf-8');\n}\n\n/*\nresult = {\n    'model_number': str(payload[12:22], 'utf-8'),\n    'manufacturer': str(payload[40:50], 'utf-8'),\n    'serial_number': str(payload[72:82], 'utf-8')\n}\n*/\n\nif (Buffer.isBuffer(msg.payload)) {\n    var data = msg.payload;\n    //var response_header = Buffer.alloc(15);\n    //data.copy(response_header,0,2,17);\n    //msg.response_header = bufFromBuffer(data, 2, 17);\n    //var response_payload_size = intFromBuffer(data, 18, 19);\n    var response_payload = bufFromBuffer(data, 19, data.length-2);\n    msg.result = {\n        'model_number': stringFromBuffer(response_payload, 12, 22),\n        'manufacturer': stringFromBuffer(response_payload, 40, 50),\n        'serial_number': stringFromBuffer(response_payload, 72, 82)\n    }\n} else {\n    msg = null;\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":240,"wires":[["ec2882d3.382388"]]},{"id":"ec2882d3.382388","type":"debug","z":"34062c9b.ab3c44","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"result","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":280,"wires":[]},{"id":"b0af667d.823ee8","type":"comment","z":"34062c9b.ab3c44","name":"README","info":"# Samil Power Inverter Data Downloader\n\nA flow to download model- and operating-data from Solarlake Samil Power Inverters.\n\n## How does it work?\n\n1.  A TCP listener is started on port 60001. The TCP listener waits for connections from Samil Power devices.\n2.  A UDP advertisement package is sent to the broadcast address of the network.\n3.  Samil Power device makes a TCP-connection to port 60001.\n4.  \"TCP Listener status\" reports a new connection and we can make the data_request.\n5.  \"TCP Listener\" receives the 1. response, we then parse the data-response and initiate the model_request.\n6.  \"TCP Listener\" receives the 2. response and we parse the model-response.\n\n## How to get started\n\n- Before running the flow for the first time you have to set the broadcast-address to that of your network. \n\n## Tested on \n\nSince I only have access to a Samil Power 10000TL-PM that's the only device I could test it with. \n\n## References\n\nThe functionality of this flow is based on the previous work by semonet. Particularly the file samil.py.\n\n[https://github.com/semonet/solar](https://github.com/semonet/solar)https://github.com/semonet/solar","x":120,"y":40,"wires":[]}]

Flow Info

Created 4 years, 4 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • catch (x1)
  • comment (x1)
  • debug (x3)
  • function (x6)
  • inject (x1)
  • status (x1)
  • switch (x2)
  • tcp in (x1)
  • tcp out (x1)
  • udp out (x1)
Other
  • tab (x1)

Tags

  • solarlake
  • samil
  • power
  • inverter
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option