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
Diego