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:Power    zpPv              "zpPv [%.0f W]"          <energy> 
Number:Power    zpBattery         "zpBattery [%.0f W]"     <energy>
Number          zpBatteryStatus   "zpBatteryStatus [%d]"
Number          zpSoc             "zpSoc [%d %%]"          <battery>
Number:Power    zpActivePower     "zpActivePower [%.0f W]" <energy>
Number          zpGridStatus      "zpGridStatus [%d]"
Number:Power    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

7 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")

2 Likes

Thanks a lot. It rocks.

In my case I have the additional challenge that I have two inverters (8KW and 5KW) and the smartmeter and the battery are connected just to the 8KW one.

Therefore I need to connect to both inverters and prepare the values. In some cases I need a sum of both values and sometimes only the initial value (like temperature).

So I take this great howto and optimize it for my requirements.
Therefore I create one array for the sensor items for my 8KW inverter (sensors8) , one for the 5KW (sensors5) and one for the summary values (sensors) to combine all needed values in the result list.

First I make some changes to

/etc/openhab/scripts/goodwejson.py

import asyncio
import goodwe

#We collect the data from two collectors, that is the reason of two IP adresses
async def get_runtime_data():
    ip_address8 = '192.168.xxx.yyy'
    ip_address5 = '192.168.xxx.zzz'



    sensors = [
        "ppv",               # PV power (W)
        "total_inverter_power",               # Inverter power (W)
        "pbattery1",         # battery power (W) + = charging, - = discharging
        "battery_mode",      # 1=standby, 2=discharge, 3=charge
        "battery_soc",       # battery state of charge (%)
        "active_power_total",      # 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 Total Energy (export) = 82.58 kWh
        "meter_e_total_imp",  # total bought or imported energy (kWh)  Meter Total Energy (import) = 288.3 kWh
        "meter_active_power_total", # grid power get from smartmeter (W): - = buy, + = sell
        "e_total_exp",		#  Total Energy (export) = 209.8 kWh
        "e_total_imp", 		#  Total Energy (import) = 0.6 kWh
#        "h_total",	 	#	 Hours Total = 156 h
        "e_day_exp",	 	#	 Today Energy (export) = 0.0 kWh
        "e_day_imp", 		 	# Today Energy (import) = 0.0 kWh
        "e_load_total", 		 	# Total Load = 241.0 kWh
        "e_load_day",		#  Today Load = 0.5 kWh
#        "temperature_air",	#  Inverter Temperature (Air) = 36.1 C
#        "temperature_module",	#  Inverter Temperature (Module) = 0.0 C
#        "temperature", 		# Inverter Temperature (Radiator) = 28.5 C
    ]

    sensors8 = [
        "ppv",               # PV power (W)
        "total_inverter_power",               # Inverter power (W)
        "pbattery1",         # battery power (W) + = charging, - = discharging
        "battery_mode",      # 1=standby, 2=discharge, 3=charge
        "battery_soc",       # battery state of charge (%)
        "active_power_total",      # 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)
        "meter_active_power_total", # grid power get from smartmeter (W): - = buy, + = sell
        "e_total_exp",		#  Total Energy (export) = 209.8 kWh
        "e_total_imp", 		#  Total Energy (import) = 0.6 kWh
        "h_total",	 	#	 Hours Total = 156 h
        "e_day_exp",	 	#	 Today Energy (export) = 0.0 kWh
        "e_day_imp", 		 	# Today Energy (import) = 0.0 kWh
        "e_load_total", 		 	# Total Load = 241.0 kWh
        "e_load_day",		#  Today Load = 0.5 kWh
        "temperature_air",	#  Inverter Temperature (Air) = 36.1 C
        "temperature_module",	#  Inverter Temperature (Module) = 0.0 C
        "temperature", 		# Inverter Temperature (Radiator) = 28.5 C
    ]
    sensors5 = [
        "ppv",               # PV power (W)
        "total_inverter_power",               # Inverter power (W)
#        "pbattery1",         # battery power (W) + = charging, - = discharging
#        "battery_mode",      # 1=standby, 2=discharge, 3=charge
#        "battery_soc",       # battery state of charge (%)
#        "active_power_total",      # 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)
        "e_total_exp",		#  Total Energy (export) = 209.8 kWh
        "e_total_imp", 		#  Total Energy (import) = 0.6 kWh
        "h_total",	 	#	 Hours Total = 156 h
        "e_day_exp",	 	#	 Today Energy (export) = 0.0 kWh
        "e_day_imp", 		 	# Today Energy (import) = 0.0 kWh
        "e_load_total", 		 	# Total Load = 241.0 kWh
        "e_load_day",		#  Today Load = 0.5 kWh
#        "meter_active_power_total", # grid power get from smartmeter (W): - = buy, + = sell
        "temperature_air",	#  Inverter Temperature (Air) = 36.1 C
        "temperature_module",	#  Inverter Temperature (Module) = 0.0 C
        "temperature", 		# Inverter Temperature (Radiator) = 28.5 C
    ]

    inverter8 = await goodwe.connect(ip_address8)
    runtime_data8 = await inverter8.read_runtime_data()

    inverter5 = await goodwe.connect(ip_address5)
    runtime_data5 = await inverter5.read_runtime_data()

    print(f'{{')
    for sensor in inverter8.sensors():
        if sensor.id_ in runtime_data8:
            if sensor.id_ in sensors8:
                print(f'"i8_{sensor.id_}": {runtime_data8[sensor.id_]},')
    for sensor in inverter5.sensors():
        if sensor.id_ in runtime_data5:
            if sensor.id_ in sensors5:
                print(f'"i5_{sensor.id_}": {runtime_data5[sensor.id_]},')
    for sensor in inverter8.sensors():
        if sensor.id_ in runtime_data8:
            if sensor.id_ in sensors5:
                value5 = runtime_data5[sensor.id_]
            else:
                value5 = 0
            if sensor.id_ in sensors8:
                value8 = runtime_data8[sensor.id_]
            else:
                value8 = 0
            sum =  value5 + value8
            if sensor.id_ in sensors:
                print(f'"{sensor.id_}": %s,' % (sum))
##and at the end some added and cumulated values:
#   "ppv",               # PV power (W)
    outputname = 'power_consumption_total'
    sum = 0
    sum =  runtime_data5['ppv'] + runtime_data8['ppv'] - runtime_data8['meter_active_power_total']
#    sum =  runtime_data5['total_inverter_power'] + runtime_data8['total_inverter_power'] - runtime_data8['meter_active_power_total']
    print(f'"{outputname}": %s,' % (sum))
    print(f'"end": 0')
    print(f"}}")

asyncio.run(get_runtime_data())

So I need also add some more items to the item list (this is the complete file):

/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:Energy   zpMeterActivePower     "zpMeterActivePower [%.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>
Number:Energy   zpEtotalConsumption       "zpEtotalConsumption [%.0f W]"    <energy>
Number:Energy   zpI5Pv              "zpI5Pv [%.0f W]"        <energy>
Number:Temperature	zpI5TempAir	"zpI5TempAir [%.0f °C]"	<temperature>	
Number:Temperature	zpI5TempModule	"zpI5TempModule [%.0f °C]"	<temperature>
Number:Temperature	zpI5TempInverter	"zpI5TempInverter [%.0f °C]"	<temperature>	
Number:Energy   zpI8Pv              "zpI8Pv [%.0f W]"        <energy>
Number:Temperature	zpI8TempAir	"zpI8TempAir [%.0f °C]"	<temperature>	
Number:Temperature	zpI8TempModule	"zpI8TempModule [%.0f °C]"	<temperature>
Number:Temperature	zpI8TempInverter	"zpI8TempInverter [%.0f °C]"	<temperature>	
Number					zpI5HourTotal		"zpI5HourTotal [%d]"
Number					zpI8HourTotal		"zpI5HourTotzpI8HourTotal [%d]"
Number:Energy   		zpMeterTotalExport	"zpMeterTotalExport [%.0f kWh]"    <energy>
Number:Energy   		zpMeterTotalImport	"zpMeterTotalImport [%.0f kWh]"    <energy>

and least adding some more rules to

/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_total",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)
    val EtotalConsumption = transform("JSONPATH","$.power_consumption_total",ZP_JSON_Out.state.toString)
    val MeterActivePower = transform("JSONPATH","$.meter_active_power_total",ZP_JSON_Out.state.toString)
    val I5Pv = transform("JSONPATH","$.i5_ppv",ZP_JSON_Out.state.toString)
    val I5TempAir = transform("JSONPATH","$.i5_temperature_air",ZP_JSON_Out.state.toString)
    val I5TempModule = transform("JSONPATH","$.i5_temperature_module",ZP_JSON_Out.state.toString)
    val I5TempInverter = transform("JSONPATH","$.i5_temperature",ZP_JSON_Out.state.toString)
    val I8Pv = transform("JSONPATH","$.i8_ppv",ZP_JSON_Out.state.toString)
    val I8TempAir = transform("JSONPATH","$.i8_temperature_air",ZP_JSON_Out.state.toString)
    val I8TempModule = transform("JSONPATH","$.i8_temperature_module",ZP_JSON_Out.state.toString)
    val I8TempInverter = transform("JSONPATH","$.i8_temperature",ZP_JSON_Out.state.toString)
    val I5HourTotal = transform("JSONPATH","$.i5_h_total",ZP_JSON_Out.state.toString)
    val I8HourTotal = transform("JSONPATH","$.i8_h_total",ZP_JSON_Out.state.toString)
    val MeterTotalExport = transform("JSONPATH","$.meter_e_total_exp",ZP_JSON_Out.state.toString)
    val MeterTotalImport = 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)
    zpEtotalConsumption.postUpdate(EtotalConsumption)
    zpMeterActivePower.postUpdate(MeterActivePower)
    zpI5Pv.postUpdate(I5Pv)
    zpI5TempAir.postUpdate(I5TempAir)
    zpI5TempModule.postUpdate(I5TempModule)
    zpI5TempInverter.postUpdate(I5TempInverter)
    zpI8Pv.postUpdate(I8Pv)
    zpI8TempAir.postUpdate(I8TempAir)
    zpI8TempModule.postUpdate(I8TempModule)
    zpI8TempInverter.postUpdate(I8TempInverter)
    zpI5HourTotal.postUpdate(I5HourTotal)
    zpI8HourTotal.postUpdate(I8HourTotal)
    zpMeterTotalExport.postUpdate(MeterTotalExport)
    zpMeterTotalImport.postUpdate(MeterTotalImport)
end

That’s it. Again thanks for the great tutorial from Diego.

2 Likes

Hi, thanks for this post. Even total OpenHAB newbie as me was able to make it working!

I have one question - is there any way to not store all the historical data for the items? Lets say I’m interested in current values od SOC only and I dont care what was the previous values. Can this be achieved soehow - well Im sure it can, but can someone please help to describe how?

Regards Jan

Hi Jan,

You can read the persistence documentation.
By default all items are stored on every change and at least every minute. If you want to change that, you can define this in a file rrd4j.persist.
I didn’t do it because the rrd4j persistence files do not take much space. And they remain at the same size.

Hi, thanks for your reply. Disk space and performance is what matters to me: Im planning to take the data each 60s lets say 10 elements → So i was concern about the file size lets say after one month it will be something around 500k records → what about the performance, what about the size? I would be ok to delete the data older than week or so …

Hi Jan,

The rrd4j database is a ‘round robin database’, meaning that the file size remains the same. As time evolves, the oldest records are mixed together by averaging (or by keeping the maximal value or other methods), and eventually the oldest records are deleted.

In my case (Openhabian), the files are located in /var/lib/openhab/persistence/rrd4j. With the default settings, for each item the .rrd file has a size of 738kB.

You can change the settings by reading the rrd4j documentation: https://www.openhab.org/addons/persistence/rrd4j/

Hi Guys,
can somebody of you guys confirm if I can get all data from the converter locally, if its offline and not connected to the cloud?
I have an offer for a solar plant that includes a goodwee converter (GoodWe ET Plus Hybrid GW5K-ET). Before accepting it, I wanted to make sure that I can operate it completely local & offline and retrieve all data locally. l would even block all internet access for the converter in my router.

Hi Diego,

I followed your manual, and all was working fine, up to the point with the bash commmand.
I get the following result in my tail-log, is this solvable?

2023-04-06 @ 16:00:13 [WARN ] - Handler StationHandler of thing semsportal:station:myPortal:solarPanels tried accessing its bridge although the handler was already disposed. 2023-04-06 @ 16:00:13 [WARN ] - Handler StationHandler tried updating the thing status although the handler was already disposed. 2023-04-06 @ 16:00:32 [WARN ] - Tried to execute 'bash /etc/openhab/scripts/goodwe.sh', but it is not contained in whitelist. 2023-04-06 @ 16:00:36 [WARN ] - Handler StationHandler of thing semsportal:station:myPortal:solarPanels tried accessing its bridge although the handler was already disposed. 2023-04-06 @ 16:00:36 [WARN ] - Handler StationHandler tried updating the thing status although the handler was already disposed. 2023-04-06 @ 16:01:32 [WARN ] - Tried to execute 'bash /etc/openhab/scripts/goodwe.sh', but it is not contained in whitelist. 2023-04-06 @ 16:02:13 [WARN ] - Handler StationHandler of thing semsportal:station:_myPortal:solarPanels tried accessing its bridge although the handler was already disposed. 2023-04-06 @ 16:02:13 [WARN ] - Handler StationHandler tried updating the thing status although the handler was already disposed.

Hi Nico

This part of the error message:

suggests that the script was not placed in the whitelist as explained above:

Hi,
I’ve followed the discription and was able to get the current status with cli.
When i go to openhab i can see the created items “zp***”.
All values show “NULL” instead of the actual values.

When i look at item ZP_JSON_Out i also see a “?” under “Channel Links”.

Can somebody help me figure out how this is happening and how i can solve this?
As mentioned at the start, when i perfrom BASH /¨/goodwe.sh i’m getting the actual values from the invertor.

Regards

I made a mistake in the code plus didn’t install the bindings mentioned here.

Prerequisites:

Install the exec binding

Install the JSONpath transformation addon (other addons)

After installating the binding the values are correct in the system.

Now i want to add them as cells in a layout but this doesn’t seem to workout.
They don’t show the value and are not linked to a thing.
I’ll keep trying in the mean time but if anyone can give me some advice on this matter it’s always welcome.

Here is my little adaption of the very useful script from @Martin_Cholkowski and @DiegoDf :

  • all values are populated as separate mqtt-messages, so no need to extract json
  • content is changed for a Goodwe15K-ET
  • installation of the python script is the same as in the first post, but no need for rules
  • things are created with .things-file
  • I create all items at once in the UI with “add equipment to model”

the script (change IP, username and password)

goodwe2mqtt.py (click to see code)
import asyncio
import goodwe
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import time

async def get_runtime_data():
    ip_address = '192.168.xx.xx'
    global jsondata
    jsondata = ""
    sensors = [
        "timestamp",                  # Timestamp [2023-10-30 10:50:01]
        "ppv",                        # PV Power [4308 W]
        "total_inverter_power",       # Total Power [2910 W]
        "active_power",               # Active Power [31 W]
        "apparent_power",             # Apparent Power [3153 VA]
        "backup_ptotal",              # Back-up Load [66 W]
        "load_ptotal",                # Load [2813 W]
        "pbattery1",                  # Battery Power [-1120 W]
        "house_consumption",          # House Consumption [3157 W]
        "battery_soc",                # Battery State of Charge [71 %]
        "active_power_total",         # Active Power Total [52 W]
        "meter_active_power_total",   # Meter Active Power Total [52 W]
        "meter_apparent_power_total", # Meter Apparent Power Total [3059 VA]    
    ]

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

    for sensor in inverter.sensors():
        if sensor.id_ in runtime_data:
            if sensor.id_ in sensors:
                jsondata = str(runtime_data[sensor.id_])

                publish.single("goodwe/" + sensor.id_, payload=jsondata, qos=0, retain=False, hostname="localhost",
                port=1883, client_id="", keepalive=60, will=None, auth={'username': 'your_username', 'password': 'yourPassword'},
                tls=None, protocol=mqtt.MQTTv311, transport="tcp")
                print(jsondata)
                print(f'"{sensor.id_}": {runtime_data[sensor.id_]},')

while True:
    asyncio.run(get_runtime_data())
    time.sleep(30) 

We need two things, one for the execution of the script and one for the mqtt-values. Here are the definitions in file-format.

goodwe.things (click so see)
Thing exec:command:goodwe2mqtt "Goodwe2mqtt" [command="/usr/bin/python3 /etc/openhab/scripts/goodwe2mqtt.py",interval=60, timeout=10, autorun=false]

Thing mqtt:topic:GoodweInv "GoodweInv" (mqtt:broker:MosquittoMqttBroker) { Channels:
  Type string : PV_timestamp "PV_timestamp" [ stateTopic = "goodwe/timestamp"]
  Type number : PV_ppv "PV_ppv" [ stateTopic = "goodwe/ppv", unit="W"]
  Type number : PV_total_inverter_power "PV_total_inverter_power" [ stateTopic = "goodwe/total_inverter_power", unit="W"]
  Type number : PV_active_power "PV_active_power" [ stateTopic = "goodwe/active_power", unit="W"]
  Type number : PV_apparent_power "PV_apparent_power" [ stateTopic = "goodwe/apparent_power", unit="VA"]
  Type number : PV_backup_ptotal "PV_backup_ptotal" [ stateTopic = "goodwe/backup_ptotal", unit="W"]
  Type number : PV_load_ptotal "PV_load_ptotal" [ stateTopic = "goodwe/load_ptotal", unit="W"]
  Type number : PV_pbattery1 "PV_pbattery1" [ stateTopic = "goodwe/pbattery1", unit="W"]
  Type number : PV_house_consumption "PV_house_consumption" [ stateTopic = "goodwe/house_consumption", unit="W"]
  Type number : PV_battery_soc "PV_battery_soc" [ stateTopic = "goodwe/battery_soc"]
  Type number : PV_active_power_total "PV_active_power_total" [ stateTopic = "goodwe/active_power_total", unit="W"]
  Type number : PV_meter_active_power_total "PV_meter_active_power_total" [ stateTopic = "goodwe/meter_active_power_total", unit="W"]
  Type number : PV_meter_apparent_power_total "PV_meter_apparent_power_total" [ stateTopic = "goodwe/meter_apparent_power_total", unit="VA"]
}

Any hints how to improve are of course welcome

1 Like