Jump to content

Reading Modbus-tcp registers of Solaredge inverter


rene

Recommended Posts

Hello, i'm a little stuck. I try to connect with my Solaredge inverter which i enabled modbus over tcp (port 502). It's works with a python script si it's not the Solaredge.

The problem is i don't get any out put. The appearance of a few // is because of testing.

Also what is confusing that sunspec mentioned about two methods of offset (0 or 40001) which i find confusing.

<?php
$id = 1; //1
$fc = 3;
$offset = 0; //70, ...107
$length = 122; //39, ..3

//Appendix A – Supported MODBUS Request Methods
//SolarEdge has implemented two methods of the MODBUS request procedure:
//MODBUS request without explicit addressing – supported by communication board CPU version 2.478 and above. For example:
//$offset_h = floor($offset/256);
//$offset_l = $offset-$offset_h*256;

//MODBUS request with explicit register addressing - supported by all communication board CPU versions. For example:
$offset_h = floor(($offset+40000)/256);
$offset_l = ($offset+40000)-$offset_h*256;


$length_h = floor($length/256);
$length_l = $length-$length_h*256;

//$crc = crc16(chr(0).chr($id).chr(0).chr($fc).chr($offset_h).chr($offset_l).chr($length_h).chr($length_l));
$crc = crc16(chr($id).chr($fc).chr($offset_h).chr($offset_l).chr($length_h).chr($length_l));
$crc_h = floor($crc/256);
$crc_l = $crc-$crc_h*256;

if ($id<10) {$id0 = "0";} else {$id0 = "";}
if ($fc<10) {$fc0 = "0";} else {$fc0 = "";}

if ($offset_h<10) {$offset_h0 = "0";} else {$offset_h0 = "";}
if ($offset_l<10) {$offset_l0 = "0";} else {$offset_l0 = "";}
if ($length_h<10) {$length_h0 = "0";} else {$length_h0 = "";}
if ($length_l<10) {$length_l0 = "0";} else {$length_l0 = "";}
if ($crc_h<10) {$crc_h0 = "0";} else {$crc_h0 = "";}
if ($crc_l<10) {$crc_l0 = "0";} else {$crc_l0 = "";}

//$senddata = chr(0).chr($id).chr(0).chr($fc).chr($offset_h).chr($offset_l).chr($length_h).chr($length_l).chr($crc_l).chr($crc_h);
$senddata = chr($id).chr($fc).chr($offset_h).chr($offset_l).chr($length_h).chr($length_l).chr($crc_l).chr($crc_h);
//without crc ...
//$senddata = chr($id).chr($fc).chr($offset_h).chr($offset_l).chr($length_h).chr($length_l);

$senddata2 = $id0.dechex($id)." ".$fc0.dechex($fc)." ".$offset_h0.dechex($offset_h).$offset_l0.dechex($offset_l)." ".$length_h0.dechex($length_h).$length_l0.dechex($length_l)." ".$crc_l0.dechex($crc_l).$crc_h0.dechex($crc_h);

//$senddata = "11 03 006B 0003 7687";
//$senddata = "1103006B00037687";

//print $senddata."<br />\n";
print $senddata2."<br />\n";

//print dechex($id)." ".dechex($fc)." ".dechex($offset_h).dechex($offset_l)." ".dechex($length_h).dechex($length_l)." ";
//print dechex($crc_l).dechex($crc_h);

//set_time_limit(0);


$fp = fsockopen("192.168.0.192", 502, $errno, $errstr, 3);
if(!$fp)
{
    print "can\'t connect modbus tcp device<br />\n";
    die();
}
else{
    print "Connected to modbus<br />\n";

    //11 03 0046 0027 xxxx  //request 40070 +27  xxxx is crc check
    //stream_set_timeout($socketHandler, 2);
    write($fp, $senddata);
    //write($fp, $senddata,strlen($senddata));
    //set_socket_blocking($fp,true);
    //fputs($fp, $senddata);

    $response = '';
    while (!feof($fp)) {
        //echo fgets($fp, 128);
        //stream_set_timeout($socketHandler, 2);
        //$line[] = fgets($fp, 128);
        $line[] = fgets($fp);
        //echo fgets($fp);
        //echo fread($fp,255);
        //$response = stream_get_contents($fp);
        //$line[] = fread($fp);
        //$response .= fgets($fp, 128); // If you expect an answer
        }
    print $response;


    foreach ($line as $key => $value) {
        print $key+$offset.":".intval($value)."<br />\n";
        }
    }  

fclose($fp);

function crc16($data) {
        $crc16 = 0xFFFF;
        for ($i = 0; $i < strlen($data); $i++)
        {
            $crc16 ^=ord($data[$i]);
             for ($j = 8; $j !=0; $j--)
            {
                if (($crc16 & 0x0001) !=0)
                {
                    $crc16 >>= 1;
                    $crc16 ^= 0xA001;
                }
                else
                $crc16 >>= 1;
            }        
        }
        return $crc16;
}
?>

 

Link to comment
Share on other sites

documentation is sunspec

for py-script you need pip install pyModbusTCP

solaredgemodbus.py

#!/usr/bin/env python
# -*- coding: utf-8 -*- read_register read 10 registers and print result 
# on stdout you can use the tiny modbus server "mbserverd" to test this 
# code mbserverd is here: https://github.com/sourceperl/mbserverd the 
# command line modbus client mbtget can also be useful mbtget is here: 
# https://github.com/sourceperl/mbtget
from pyModbusTCP.client import ModbusClient
import time
from datetime import datetime
#import http.server
#import socketserver
#from flask import Flask

#app = Flask(__name__)

SERVER_HOST = "192.168.0.192"
SERVER_PORT = 502

c = ModbusClient()

# uncomment this line to see debug message#
#c.debug(True) define modbus 

# server host, port
c.host(SERVER_HOST)
c.port(SERVER_PORT)


#@app.route('/')
#def index():
#    return 'Hello world'

#if __name__ == '__main__':
#    app.run(debug=True, host='0.0.0.0')


# open or reconnect TCP to server
if not c.is_open():
    if not c.open():
        print("unable to connect to "+SERVER_HOST+":"+str(SERVER_PORT))
# if open() is ok, read register (modbus function 0x03)
if c.is_open():
    # read 10 registers at address 0, store result in regs list
    regs = c.read_holding_registers(40070, 39)
	  #regs = c.read_holding_registers(40097, 4)
    # if success display registers
    if regs:
        power = regs[13] # 40084 I_AC_Power
        powersf = regs[14] # 40085 I_AC_Power_SF
        if powersf>32768:
            powersf = (65536 - powersf) * -1
        power = round(power*(10**powersf),3)
        lifetime = regs[24]+(65536*regs[23]) # 40094 I_AC_Energy_WH
        lifetimesf = regs[25] # 40096 I_AC_Energy_WH_SF
        if lifetimesf>32768:
            lifetimesf = (66536 - lifetimesf) * -1
        lifetime = lifetime*(10**lifetimesf)
        status = regs[37] # 40108 I_Status
        #print("reg ad 0 to 9: "+str(regs)) print {"reg 40084= 
        #"+str(regs[15])} print power print powersf print 
        #(str(lifetime) + "wh") print lifetimesf
        codes = ["NA", "OFF", "SLEEP", "START", "ON", "PROD", "STOP", "FAULT", "SETUP"]
        #print (codes[status])
        now = datetime.now()
        current_time = now.strftime("%H:%M:%S")
        #print (current_time + "\t" + str(power) + "wh\t\t" + str(lifetime) + "wh\t" + codes[status])
        print ("current=" + str(power) + "&lifetime=" + str(lifetime) + "&status=" + codes[status])
        #i = 0
        #40108 I_Status 1=OFF, 2=SLEEP, 3=START, 4=ON, 5=PROD, 6=STOP, 
        #7=FAULT, 8=SETUP print("----------------------")
        #while i < len(regs):
        #print (str(i+40071) + " : " + str(regs[i])) i += 
        #1
        #sleep 2s before next polling

and with solaredgemodbus.php i run and output the python script

<?php
//print "Result of solaredgemodbus.py is:<br />\n";
//$read = escapeshellcmd("python solaredgemodbus.py");
$command = escapeshellcmd('python3 solaredgemodbus.py');
$read = shell_exec($command);
if (substr($read, 0,7) == "current") {
  print $read;

  /*$ts = time();
  $time = date('H:i:s',$ts);
  $date = date('d-m-Y',$ts);
  $fp=fopen("output.log","a");
    if (fwrite($fp, $time."\t".$date."\t".$read)) {
      //print "writing data to output.log succes.";
      } else {
      //print "writing data to output.log failed.";
    }
  fclose ($fp);*/

  } else {
  // make log
  $ts = time();
  $time = date('H:i:s',$ts);
  $date = date('d-m-Y',$ts);
  $fp=fopen("output.log","a");
    if (fwrite($fp, $time."\t".$date."\t".$read."\n")) {
    //print "writing data to output.log succes.";
    } else {
    //print "writing data to output.log failed.";
    }
  fclose ($fp);
  }
?>

and that's the reason i want to eliminate the python-script..... it's too much hassle.

for the sunspec specifications just google on sunspec solaredge modbus.

Link to comment
Share on other sites

Without me trying to write the script myself, I see three options:

1. Dig deeper into the Python code to find out exactly what it does. Specifically, the ModbusClient. Translate everything in there directly to PHP code. Same class names, same method names, same everything.
2. Use a packet sniffer to see what the Python script sends versus what your script (for the same information) sends.
3. Keep the Python script, and write your output.log stuff in Python instead of PHP.

Link to comment
Share on other sites

the pymodbus.py library is somewhere in the os.

I just want to write the php code from scratch. I assume the communicating is thru websocket protocol.

How hard can it be?

How many members this forum have? I hoped that there would be members that had already did something with modbus-tcp protocol or even better....have a solaredge converter at home. Would it be great for those to see there values realtime and being independent from the solaredge-servers?

But i place some print "testx<br />\n";  and noticed the script wouldnt passed the write function. and write must be fwrite i think.

I also now experimenting with

$fp = stream_socket_client("tcp://192.168.0.192:502", $errno, $errstr, 3);

but still no output......

which raise the question for me, is it tcp or udp.

i thought modbus-tcp is a serial protocol encapsulated in a tcp protocol and it's just sending the codes and after that receiving the codes .....

Unfortunally modbus is not common for non-industry so not much good examples i.c.w. php.

But again, it must be possible .......... right?

----------------------------------

btw: this is output from phpversion()

-------

Additional Modules

Module Name

Environment

--------

sockets

Sockets Supportenabled

sodium

Edited by rene
Link to comment
Share on other sites

38 minutes ago, rene said:

I just want to write the php code from scratch. I assume the communicating is thru websocket protocol.

How hard can it be?

Hahaha.

"I want to build a race car from scratch. It has four tires, a steering wheel that pivots them, and windshield wipers. How hard can it be?"

You aren't just writing PHP code. You're writing PHP code that must observe a binary protocol communicating through a TCP socket. That is something 99% of PHP developers never have to think about in their careers.
If you have a reliable spec that is relatively easy to understand then perhaps you should start there. Build your code from the ground up by dealing with the little things here and there: a function to send a specific message, a function to read a specific message, a class holding constants for all those magic numbers like 40084 and 40108. Each one of those can be written more or less by itself, and when you have enough of them, you can piece them together into something that actually does the thing you want done.

 

38 minutes ago, rene said:

But i place some print "testx<br />\n";  and noticed the script wouldnt passed the write function. and write must be fwrite i think.

Now that is something we're suitable to deal with.

If you didn't create it yourself then yes: PHP doesn't have a "write" function. You will be wanting fwrite.

 

38 minutes ago, rene said:

which raise the question for me, is it tcp or udp.

The Python script says TCP...

If it's not TCP then the initial connection to the device will fail. Which is a really easy thing to notice. And since you haven't noticed it, I imagine it hasn't happened.

Link to comment
Share on other sites

As a protocol goes, Modbus is very simple, ascii, really old, and well documented.  I've never done anything with Modbus/TCP but did quite a bit with Modbus/RTU (serial) years ago.  Pretty much just an address, write/read command, and data with some meta start and end bytes.  It is my understanding that Modbus/TCP is pretty much the same thing but wrapped in some sort of IP packet.  You will either want to be a master/client or slave/server (master/slave for serial and client/server for IP).  For a master/client, you put some bytes on the wire and the device you address either writes that data to itself or returns some data (been about 20 years and forgot the details).  Slave/servers listen for the start bytes and then capture some amount of bytes.  Pay attention to the meta start and checksum bytes.

Assuming you are going IP and want to start from scratch, I would first create either a socket server or client (see https://reactphp.org/socket/#quickstart-example).  You will also want to use something like wireshark to better understand the data.  Alternatively, you can just search "modbus php" and might find something a little more baked as a starting point.

Link to comment
Share on other sites

when you google on modbus php you end up with only one from Jan Krakora which gives me a error about connection which google gives unfortunally not a solution.

But i will study that link of reactphp tomorrow.

It's indeed something with socket (is there a diffrence with websocket?) .......

Link to comment
Share on other sites

I tried:

 

$host = "192.168.0.192";
$port = 502;
// No Timeout 
set_time_limit(0);

$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socket\n");

socket_connect($socket, $host, $port) or die("Could not connect to server\n");

socket_write($socket, $senddata) or die("Could not send data to server\n");

$result = socket_recv($socket, $buf , 2000 , 0 );

echo "Reply From Server  :".strlen($result)." / ".strlen($result)." / ".strlen($buf);

socket_close($socket);
print "\n\n<br />\ntest1<br />\n";

but i still don't get output ....

11 03 138c6 0027 ea1d
Reply From Server :1 / 1 / 0
test1

Link to comment
Share on other sites

On 12/3/2020 at 7:08 PM, rene said:

was a real brain-cracker but i manage to solves it ......

 

<=closed =>

Duuude!! Don't just leave us hanging like that.

Some of us have an identical issue (no response from the inverter no matter what we try), please add a post indicating how you managed to solve your issue.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.