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. Since Bookworm it has to be installed in a virtual environment or, as here, with the âbreak-system-packages option:
sudo pip3 install goodwe --break-system-packages
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