Connect OH to Goodwe via UDP

We have a Goodwe ES inverter connected to our wifi network and an app (PVMaster) on the phone to obtain information such as battery SOC, PV generation, load etc. I want to pull this information into OH to automate things like the pool pump. To date the only information I’ve found online involves the SEMS web portal where this information is saved (at something like 5 minute intervals). My thinking was that if the app can get the information via wifi, it must be possible for OH to do the same. Unfortunately I’m an amateur (to put it lightly) when it comes to these things.

Then someone cracked it for HA:


Basically the app sends a message to the inverter’s IP on port 8899 (sometimes 48899) and the inverter sends the information back to the phone using this port. The Python code for HA includes the deciphering of this code. Now I want to replicate this process in OH but I’m running out of skills quickly. I’m not familiar with UDP (or TCP for that matter) or Python and I’m struggling to find my way. I’m hoping the TCP/UDP binding can obtain the information and I’ll need help to transform this information into the various parameters.

I know I’m asking a LOT (as far as I can see, this hasn’t been done before for a Goodwe) but I’m sure a lot of people can benefit from this development. Any help or pointers where I can quickly pick up the required knowledge?

So I think I’m making a bit of progress. I ran the Python code “inverter_test.py” from my laptop (OH runs on a separate RPI) and it looks like the code gets a response from the Goodwe:

It doesn’t get the response expected but that I can figure out later. For now I just want to get the info into OH.

So I installed the TCP/UDP binding and created the following items:
String GoodweData { udp="<[192.168.68.110:8899:]"}
String GoodweRequest { udp=">[192.168.68.110:8899:]"}

In the rules I created the following line to run every minute:
sendCommand(GoodweRequest, “aa55c07f0102000241”)

I can see this line run but “GoodweData” does not get an update. I suspect I might be getting the whole string vs hex format wrong but I just don’t know. Any ideas would be appreciated.

Using Packet Sender om my laptop, I mimicked the Goodwe to see if I can get the UDP connection to work but the IP address provided seemed wrong. This may have something to do with the way our network is set up.

So as an alternative I pulled out a wemos board, found some UDP code and added my MQTT code. The wemos successfully requests the data from the Goodwe and passes it on to OH via MQTT. It’s a bit of a workaround but it works!

1 Like

I translate the python plugin into a php-skript (only for my ET-Model), which irun every minute with exec-binding (dont forget whitelisting it). For ES-Model you have to change the script.

  1. change $oh_url and static $pbx_ip to your IPs

  2. create items in PaperUi with the names from $Bat and $Dat arrays.

  3. Insert new Frame in sitemap:
    Frame label=“PV” {
    Text item=PV1_Power label=“Leistung [%.1f W]”
    Text item=Total_Power label=“Last [%.1f W]”
    Text item=House_Comsumption label=“Netz [%.1f W]”
    Text item=Inverter_Temperature label=“Invertertemperatur [%.1f °C]”
    Text item=Battery_Temperature label=“Batterietemperatur [%.1f °C]”
    Text item=Battery_Power label=“Batterieleistung [%.1f W]”
    Text item=Battery_State_of_Charge label=“Batterieladung [%.1f]”
    }

      #!/usr/bin/php
     <?php
     $socket=new tcp_mitel_smdr('bat');
     $resp=str_split($socket->hex,6);
    
     $Bat=array("Battery_Temperature"=>$resp[3],"Battery Charge Limit"=>$resp[4],"Battery Discharge Limit"=>$resp[5],"Battery Status"=>$resp[6],"Battery_State_of_Charge"=>$resp[7],"Battery_State_of_Health"=>$resp[8]);
    
     foreach ($Bat as $index => $value) {
     $value=hexdec($value);
     switch($index) {
                 case 'Battery_State_of_Charge':
                     setOHItemState($index,$value);
                     break;
                 
             
             case 'Battery_Temperature':
                 $value=$value/10;
                 setOHItemState($index,$value);
                 break;
                 }
     echo $index." = ".$value."\n";
     }
    
     $socket=new tcp_mitel_smdr('dat');
     $resp=str_split($socket->hex,3);
    
     $Dat=array("PV1_Voltage"=>$resp[6].$resp[7],"PV1_Current"=>$resp[8].$resp[9],"PV1_Power"=>$resp[10].$resp[11].$resp[12].$resp[13],"Total_WR_Power"=>$resp[74].$resp[75].$resp[76].$resp[77],"Active_Power"=>$resp[78].$resp[79].$resp[80].$resp[81],"Load_L1"=>$resp[126].$resp[127].$resp[128].$resp[129],"Load_L2"=>$resp[130].$resp[131].$resp[132].$resp[133],"Load_L3"=>$resp[134].$resp[135].$resp[136].$resp[137],"Inverter_Temperature"=>$resp[152].$resp[153],"Battery_Voltage"=>$resp[160].$resp[161],"Battery_Current"=>$resp[162].$resp[163],"Battery_Mode"=>$resp[168].$resp[169],"Battery_Power"=>"0","Total_Power"=>"0","House_Comsumption"=>"0");
     // 1: "Spare",2: "Discharge",3: "Charge"
     $watt=array();
    
     foreach ($Dat as $index => $value) {
     $value=hexdec($value);
    
     switch($index) {
                 /*case 'PV1_Power':
                     $value=$value/100;
                
                 break;*/
             case 'Inverter_Temperature':
                 $value=$value/10;
                 
                 break;
                 
             case 'Active_Power':
                 if ($value > 32768){$value=$value- 65535;}
               //  $value=$value/100;
                 
                 break;
             case 'Battery_Current':
                 //$value=hexdec($value);
                 if ($value > 32768){$value=$value- 65535;}
                 $value=$value/10;
                 
                 break;
                
             case 'Battery_Power':
                 $value=($watt[9]*$watt[10])/10;
                 
                 break;
                
             case 'Total_Power':
                 $value=$watt[5]+$watt[6]+$watt[7];
                 
                 break;
                
             case 'House_Comsumption':
                 $value=(($watt[3]+$watt[12]+$watt[4])-$watt[13]);///100;
                 
                 break;
                           
             default:
             
                 break;
              }
    
              array_push($watt,$value);
              echo $index." = ".$value."\n";
             setOHItemState($index,$value);
     }
    
     function setOHItemState($item, $state) 
     { 	$oh_url = "192.168.211.56";
     $oh_port = "8080";
     $return = false; 
     $headers = array(); 	
     $headers[] = 'Content-Type: text/plain'; 	
     $headers[] = 'Accept: application/json'; 
     $ch = curl_init(); 
     curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($ch, CURLOPT_URL, "http://$oh_url:$oh_port/rest/items/$item"); 	
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 	
      curl_setopt($ch, CURLOPT_POST, 1); 	
      curl_setopt($ch, CURLOPT_POSTFIELDS, $state); 
      curl_exec($ch); 	 
      if (!curl_errno($ch)) { $return = true; } 	
      curl_close($ch); 	 	
      return $return;	 
      }
       
     function getOHItemState($oh_url, $oh_port, $item) 
     { 	$state = 'OFF'; $headers = array(); 	$headers[] = 'Content-Type: text/plain'; 	$headers[] = 'Accept: application/json'; 
     $ch = curl_init(); 
     curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
      curl_setopt($ch, CURLOPT_URL, "http://$oh_url:$oh_port/rest/items/$item"); 	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($ch); 
     if (!curl_errno($ch)) { $json = json_decode($response, true); 		if(isset($json['state'])) { 			$state = $json['state']; 		} } 	curl_close($ch); 	 	
     return $state; } 
    
     class tcp_mitel_smdr {
      
     static $pbx_ip = '192.168.211.104';
     static $pbx_port   = '8899'; 
     static $pbx_timeout = 30;
     static $pbx_flags;
     public $hex = '';
     private $pbx;
     
     function __construct($run='') {
         $this->pbx=false;
         switch($run) {
             case 'bat':
                 $this->loop_socket("\xF7\x03\x90\x88\x00\x0B\xBD\xB1");
                 break;
             case 'dat':
                 $this->loop_socket("\xF7\x03\x89\x1C\x00\x7D\x7A\xE7");
                 break;
         }
        
     }
    
     function pbx_connect($msg){
         //Connect to Server
         $errno = '';
         $errstr = '';
         $this->pbx = stream_socket_client("udp://".tcp_mitel_smdr::$pbx_ip.":".tcp_mitel_smdr::$pbx_port."", $errno, $errstr, tcp_mitel_smdr::$pbx_timeout);
                 
         fwrite($this->pbx, $msg);
         stream_set_timeout($this->pbx, 1);
     }
    
     function loop_socket($msg){
         $disconnected=0;
         while($disconnected<1){
             $disconnected +=$this->process_socket($msg);
           //  echo('pbx disconnected, reconnecting');
         }
          fclose($this->pbx);
     }
    
     function process_socket($msg){
         $this->pbx_connect($msg);
         //Receive response from server. Loop until the response is finished
         //while ($this->pbx && !feof($this->pbx)) {
             // read from pbx
             $callstring = fread($this->pbx, 257);
             // the string may just be a PING. 
             if($callstring){
                 $this->hex=$this->echoit($callstring);
               
                 // write to mysql
                 #$smdr = new db_class();
                 #$smdr->save_data($callstring);
             }
         //}
         return 1;
     }
     
     
     function echoit($resp) {
       $hex = '';
       $ascii=substr($resp,5,strlen($resp)-2);
       for ($i = 0; $i < strlen($ascii); $i++) {
         $byte = strtoupper(dechex(ord($ascii{$i})));
         $byte = str_repeat('0', 2 - strlen($byte)).$byte;
         $hex.=$byte." ";
         }
       //var_dump($hex);
       return $hex;
         }
     }
    
     ?>
1 Like

Hey, I know this is a bit of an older post, but would you be willing to share the Wemos code? I want to connect my Wemos board to the inverter using UDP, just like you did.