Connecting Goodwe Solar Panel Inverter Directly to Openhab

In october 2021 some solar pannels and a battery were installed at our home, with a Goodwe GW5000-EH inverter.

It has a web interface at https://www.semsportal.com. But of course I wanted to monitor the inverter from Openhab 3.2.

There is a Sems binding available, with a few channels. I wanted more. @RogerG007 proposed a solution that worked very well for me:
https://community.openhab.org/t/connecting-goodwe-solar-panel-inverter-to-openhab/85480

And with the new fixed canvas layout in OH3.2, I was able to design a nice schematic layout for the installation.

Eventually I found a Python library, originally designed for Home Assistant by Marcel Blijleven, but later made available for general use, that can communicate directly with the inverter. This has two advantages:

  • Works even without an internet connection

  • No more processing delay of the Goodwe servers. This delay amounts to about one minute

It is available at https://github.com/marcelblijleven/goodwe

Prerequisites:

  • Install the exec binding

  • Install the JSONpath transformation addon (other addons)

Pip3 is the package manager of Python3 and can be installed with:

sudo apt install python3-pip

Then you can install the library:

sudo pip3 install goodwe

The sudo is important as it installs the library for all users.

You have to find out the ip address of your inverter and put it in this code:

import asyncio
import goodwe


async def get_runtime_data():
    ip_address = '192.168.1.120'

    inverter = await goodwe.connect(ip_address)
    runtime_data = await inverter.read_runtime_data()

    for sensor in inverter.sensors():
        if sensor.id_ in runtime_data:
            print(f"{sensor.id_}: \t\t {sensor.name} = {runtime_data[sensor.id_]} {sensor.unit}")


asyncio.run(get_runtime_data())

If you call this file goodwetest.py, you can execute it with

python3 goodwetest.py

If all goes well, you will see a long list of ‘sensors’ and their values. There you have to choose which ones are important for you and put them in another python file (eg. goodwejson.py):

import asyncio
import goodwe

async def get_runtime_data():
    ip_address = '192.168.1.120'
    sensors = [
        "ppv",               # PV power (W)
        "pbattery1",         # battery power (W) + = charging, - = discharging
        "battery_mode",      # 1=standby, 2=discharge, 3=charge
        "battery_soc",       # battery state of charge (%)
        "active_power",      # grid power (W): - = buy, + = sell
        "grid_in_out",       # 1=sell or export, 2=buy or import
        "house_consumption", # own consumption (W)
        "e_day",             # today's PV energy production (kWh)
        "e_total",           # total PV energy production (kWh)
        "meter_e_total_exp", # total sold (exported) energy (kWh)
        "meter_e_total_imp"  # total bought or imported energy (kWh)
    ]

    inverter = await goodwe.connect(ip_address)
    runtime_data = await inverter.read_runtime_data()
    print(f'{{')
    for sensor in inverter.sensors():
        if sensor.id_ in runtime_data:
            if sensor.id_ in sensors:
                print(f'"{sensor.id_}": {runtime_data[sensor.id_]},')
    print(f'"end": 0')
    print(f"}}")

asyncio.run(get_runtime_data())

If you execute this file with:

python3 goodwejson.py

you will see a much shorter result in json format.

Next you should create a bash file (eg. goodwe.sh):

#!/bin/bash
 
RESULT=$(python3 /etc/openhab/scripts/goodwejson.py)

echo "$RESULT" | tr '\n' ' '

and make it executable with:

sudo chmod 744 goodwe.sh

Both files goodwejson.py and goodwe.sh should be placed in /etc/openhab/scripts.

As a security measure the bash file should be listed in /etc/openhab/misc/exec.whitelist:

bash /etc/openhab/scripts/goodwe.sh

You must define a thing for the exec binding

sudo nano /etc/openhab/things/exec.things

with this content:

exec:command:goodwe_json [command="bash /etc/openhab/scripts/goodwe.sh",interval=60, timeout=10, autorun=false]

The interval is in seconds. The interval is 60 seconds, maybe this can be shortened.

Now the items can be defined (eg. /etc/openhab/items/goodwe.items):

String ZP_JSON_Out "[%s]" {channel="exec:command:goodwe_json:output"}
Number:Energy   zpPv              "zpPv [%.0f W]"        <energy> 
Number:Energy   zpBattery         "zpBattery [%.0f W]"   <energy>
Number          zpBatteryStatus   "zpBatteryStatus [%d]"
Number          zpSoc             "zpSoc [%d %%]"        <battery>
Number:Energy   zpActivePower     "zpActivePower [%.0f W]" <energy>
Number          zpGridStatus      "zpGridStatus [%d]"
Number:Energy   zpConsumption     "zpConsumption [%.0f W]" <energy>
Number:Energy   zpEday            "zpEday [%.1f kWh]"    <energy>
Number:Energy   zpEtotal          "zpEtotal [%.0f kWh]"    <energy>
Number:Energy   zpEtotalExp       "zpEtotalExp [%.0f kWh]"    <energy>
Number:Energy   zpEtotalImp       "zpEtotalImp [%.0f kWh]"    <energy> 

The first item ZP_JSON_out receives the output of the bash file goodwe.sh

Now we have to transform this output into item values. This is done by a rules file /etc/openhab/rules/goodwe.rules:

rule "ZP JSON transform" 
when 
    Item ZP_JSON_Out changed 
then
    val Pv = transform("JSONPATH","$.ppv",ZP_JSON_Out.state.toString)
    val Battery  = transform("JSONPATH","$.pbattery1",ZP_JSON_Out.state.toString)  
    val BatteryStatus =  transform("JSONPATH","$.battery_mode",ZP_JSON_Out.state.toString) 
    val Soc = transform("JSONPATH","$.battery_soc",ZP_JSON_Out.state.toString)
    val ActivePower = transform("JSONPATH","$.active_power",ZP_JSON_Out.state.toString)
    val GridStatus = transform("JSONPATH","$.grid_in_out",ZP_JSON_Out.state.toString) 
    val Consumption = transform("JSONPATH","$.house_consumption",ZP_JSON_Out.state.toString)
    val Eday = transform("JSONPATH","$.e_day",ZP_JSON_Out.state.toString)
    val Etotal = transform("JSONPATH","$.e_total",ZP_JSON_Out.state.toString)
    val EtotalExp = transform("JSONPATH","$.meter_e_total_exp",ZP_JSON_Out.state.toString)
    val EtotalImp = transform("JSONPATH","$.meter_e_total_imp",ZP_JSON_Out.state.toString)
    
    zpPv.postUpdate(Pv) 
    zpBattery.postUpdate(Battery) 
    zpBatteryStatus.postUpdate(BatteryStatus) 
    zpSoc.postUpdate(Soc)    
    zpActivePower.postUpdate(ActivePower)
    zpGridStatus.postUpdate(GridStatus)  
    zpConsumption.postUpdate(Consumption)
    zpEday.postUpdate(Eday)
    zpEtotal.postUpdate(Etotal)
    zpEtotalExp.postUpdate(EtotalExp)
    zpEtotalImp.postUpdate(EtotalImp)
end

This should work :wink:
Diego

5 Likes

Awesome … many thanks. Works as designed. Just as a hint → the whitelist file under /misc is not being shown if u use winscp (take the putty and u´ll find it)

Hi Stephan,

Glad to know that you can use this.

With Winscp the right pane does not refresh automatically. You can do it manually with the button with the two green arrows or with CTRL-R:

I tried the above instruction but I cannot get it to work. First when I run Python3 goodwejson.py I get all zero values except PV1 Voltage 2.1V is this correct?

vpv1:            PV1 Voltage = 2.1 V
ipv1:            PV1 Current = 0.0 A
ppv1:            PV1 Power = 0 W
pv1_mode:                PV1 Mode code = 0
pv1_mode_label:                  PV1 Mode = PV panels not connected
vpv2:            PV2 Voltage = 0.0 V
ipv2:            PV2 Current = 0.0 A
ppv2:            PV2 Power = 0 W
pv2_mode:                PV2 Mode code = 0
pv2_mode_label:                  PV2 Mode = PV panels not connected
ppv:             PV Power = 0 W
vbattery1:               Battery Voltage = 0.0 V
battery_status:                  Battery Status = 0
battery_temperature:             Battery Temperature = 0.0 C
ibattery1:               Battery Current = 0.0 A
pbattery1:               Battery Power = 0 W
battery_charge_limit:            Battery Charge Limit = 0 A
battery_discharge_limit:                 Battery Discharge Limit = 0 A
battery_error:           Battery Error Code = 0
battery_soc:             Battery State of Charge = 0 %
battery_soh:             Battery State of Health = 0 %
battery_mode:            Battery Mode code = 0
battery_mode_label:              Battery Mode = No battery
battery_warning:                 Battery Warning = 0
meter_status:            Meter Status code = 0
vgrid:           On-grid Voltage = 0.0 V
igrid:           On-grid Current = 0.0 A
pgrid:           On-grid Export Power = 0 W
fgrid:           On-grid Frequency = 0.0 Hz
grid_mode:               Work Mode code = 0
grid_mode_label:                 Work Mode = Inverter Off - Standby
vload:           Back-up Voltage = 0.0 V
iload:           Back-up Current = 0.0 A
pload:           On-grid Power = 0 W
fload:           Back-up Frequency = 0.0 Hz
load_mode:               Load Mode code = 0
load_mode_label:                 Load Mode = Inverter and the load is disconnected
work_mode:               Energy Mode code = 0
work_mode_label:                 Energy Mode = Check Mode
temperature:             Inverter Temperature = 0.0 C
error_codes:             Error Codes = 0
e_total:                 Total PV Generation = 0.0 kWh
h_total:                 Hours Total = 0 h
e_day:           Today's PV Generation = 0.0 kWh
e_load_day:              Today's Load = 0.0 kWh
e_load_total:            Total Load = 0.0 kWh
total_power:             Total Power = 0 W
effective_work_mode:             Effective Work Mode code = 0
effective_relay_control:                 Effective Relay Control = 0
grid_in_out:             On-grid Mode code = 0
grid_in_out_label:               On-grid Mode = Idle
pback_up:                Back-up Power = 0 W
plant_power:             Plant Power = 0 W
meter_power_factor:              Meter Power Factor = 0.0
diagnose_result:                 Diag Status Code = 0
diagnose_result_label:           Diag Status =
house_consumption:               House Consumption = 0 W

While the sems portal tells me this:

  • ModelGW5000D-NS

  • SN15000DSN162W0249

  • Checkcode064743

  • Capacity5kW

  • Connected04.17.2017 15:59:41

  • Power0.165kW

  • Output Voltage227.6V

  • AC Current0.9A

  • AC Frequency49.99Hz

  • Inner Temperature16.3

  • DC voltage/current 1 297.9/0.1V/A

  • DC voltage/current 2 277.3/0.1V/A

  • DC voltage/current 3 –V/A

  • DC voltage/current 4 –V/A

  • String current 1 –A

  • String current 2 –A

  • String current 3 –A

  • String current 4 –A

Could someone tell we what is wrong?

I now see that the sems portal has data which is 3 hour old, so it does not update any more, could this be the cause that I tried to connect via UPD with the python script? I could be that I did this 3 hours ago.

Thanks

Hi Frank,

My model is GW5000-EH.

It seems that there is no communication anymore with your inverter. Maybe you can try to reset the communication (on my model there is a WiFi reset button for this).

According to this site https://github.com/marcelblijleven/goodwe, your inverter should be compatible, although there is a comment:

If you can’t communicate with the inverter despite your model is listed above, it is possible you have old ARM firmware version. You should ask manufacturer support to upgrade your ARM firmware (not just inverter firmware) to be able to communicate with the inveter via UDP.

As your inverter dates from 2017, there is a possibility that you are unlucky :slightly_frowning_face:

Hello, i have error in log:

08:55:23.210 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'ZP_JSON_Out' changed from { "ppv": 694, "pbattery1": -278, "battery_soc": 19, "battery_mode": 3, "e_total": 4556.0, "e_day": 0.2, "grid_in_out": 0, "house_consumption": 407, "end": 0 } to { "ppv": 856, "pbattery1": -417, "battery_soc": 19, "battery_mode": 3, "e_total": 4556.0, "e_day": 0.2, "grid_in_out": 0, "house_consumption": 435, "end": 0 }
08:55:23.259 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'zpPv' changed from 694 W to 856 W
08:55:23.262 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'zpBattery' changed from -278 W to -417 W
08:55:23.265 [WARN ] [ab.core.model.script.actions.BusEvent] - Cannot convert '{ "ppv": 856, "pbattery1": -417, "battery_soc": 19, "battery_mode": 3, "e_total": 4556.0, "e_day": 0.2, "grid_in_out": 0, "house_consumption": 435, "end": 0 }' to a state type which item 'zpActivePower' accepts: [DecimalType, QuantityType, UnDefType].
08:55:23.276 [WARN ] [ab.core.model.script.actions.BusEvent] - Cannot convert '{ "ppv": 856, "pbattery1": -417, "battery_soc": 19, "battery_mode": 3, "e_total": 4556.0, "e_day": 0.2, "grid_in_out": 0, "house_consumption": 435, "end": 0 }' to a state type which item 'zpEtotalExp' accepts: [DecimalType, QuantityType, UnDefType].
08:55:23.279 [WARN ] [ab.core.model.script.actions.BusEvent] - Cannot convert '{ "ppv": 856, "pbattery1": -417, "battery_soc": 19, "battery_mode": 3, "e_total": 4556.0, "e_day": 0.2, "grid_in_out": 0, "house_consumption": 435, "end": 0 }' to a state type which item 'zpEtotalImp' accepts: [DecimalType, QuantityType, UnDefType].

Hi Jan,

The communication with the inverter is ok as some values are read succesfully. The errors mean that for your model of inverter there are no variables “active_power”, “meter_e_total_exp” and “meter_e_total_imp”.

The goodwe python library works well with different inverter models, but the names of what you want to read can be different.

I would suggest that you find out which values are interesting for you by executing the goodwetest.py script:

python3 goodwetest.py > goodwetest.txt

This gives you a long list of values, that you can compare with what Semsportal shows. Not easy because the values constantly fluctuate.

By looking at the code of the library I guess you have a ES, EM or BP model. I would suggest “pgrid” as an alternative for “active_power”. I found no equivalents for the other two values.

Hi Everyone.
I updated the original code with the MQTT library which allows it to send the data to MQTT server for people who use MQTT. Please change the IP address and topic as needed.

import asyncio
import goodwe
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish



async def get_runtime_data():
    ip_address = '192.168.1.204'
    global jsondata
    jsondata = ""
    sensors = [
        "ppv",             # PV power (W)
        "vpv1",            # PV1 Voltage (V)
        "ipv1",            # PV1 Current (A)
        "ppv1",            # PV1 Power (W)
        "vpv2",            # PV2 Voltage (V)
        "ipv2",            # PV2 Current (A)
        "ppv2",            # PV2 Power = (W)
        "vline1",          # On-grid L1-L2 Voltage (V)
        "vgrid1",          # On-grid L1 Voltage (V)
        "igrid1",          # On-grid L1 Current (A)
        "fgrid1",          # On-grid L1 Frequency (Hz)
        "pgrid1",          # On-grid L1 Power (W)
        "ppv",             # PV Power (W)
        "temperature",     # Inverter Temperature (C)
        "e_day",             # today's PV energy production (kWh)
        "e_total",           # total PV energy production (kWh)
    ]

    inverter = await goodwe.connect(ip_address)
    runtime_data = await inverter.read_runtime_data()
   # print(f'{{')
    jsondata='{'
    for sensor in inverter.sensors():
        if sensor.id_ in runtime_data:
            if sensor.id_ in sensors:
                #print(f'"{sensor.id_}": {runtime_data[sensor.id_]},')
                jsondata = jsondata + '"' + sensor.id_ + '":' + str(runtime_data[sensor.id_]) + ','
   # print(f'"end": 0')
   # print(f"}}")
    jsondata = jsondata + "}"
    
while True:
    asyncio.run(get_runtime_data())
# print(jsondata)
    publish.single("goodwe/", payload=jsondata, qos=0, retain=False, hostname="localhost",
    port=1883, client_id="", keepalive=60, will=None, auth=None, tls=None,
    protocol=mqtt.MQTTv311, transport="tcp")

1 Like