Connecting Goodwe Solar Panel Inverter to Openhab

I saw some requests on how to connect the Goodwe Solar Panel Inverter to openhab using the Goodwe’s API. Therefore, I would like to share my solution with you.

Some prerequisites:

  • You should have the SEMSPORTAL up and running with your Inverter
  • I propose you set up a second user in the portal for openhab. (if you log in with the same user twice at the same time, the portal will terminate the session.)
  • Find your PowerStationId in the SEMSPORTAL. You can see it in the URL when you log in and it looks something like this: abcdef10-8ccf-23df-8d12-b123456b123b
  • Install the exec binding and JSON transformation binding in openhab.

First of all, you need to write a small shell script. Make sure you put in you semsportal accountname, password and your PowerStationID:

/etc/openhab2/scripts/semsapi.sh

#!/bin/bash

RESULT=$(curl https://www.semsportal.com/api/v2/Common/CrossLogin --silent --header "Content-Type: application/json" --header "Connect: keep-alive" --header "User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)" --header "Accept-Language: en;q=1" --header "Token: {\"version\":\"v2.1.0\",\"client\":\"ios\",\"language\":\"en\"}" --data-binary "{\"account\":\"secondopenhabuser@gmail.com\",\"pwd\":\"yourpassword\"}")

HEADER=$(echo "$RESULT"|jq ".data"|tr '\n' ' ')

RETURN=$(curl https://www.semsportal.com/api/v2/PowerStation/GetMonitorDetailByPowerstationId --silent --header "Content-Type: application/json" --header "Accept: */*" --header "User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)" --header "Accept-Language: DE;q=1" --header 'Token: '"$HEADER" --data-binary "{\"powerStationId\":\"abcdef10-8ccf-23df-8d12-b123456b123b\"}")

echo "$RETURN" | jq ".data"

You can minimize the output by tweaking with the jq command at the end. For now, it returns all available data. Make sure the script is executable and test it. You should see a long JSON-Output with all the values from your inverter.

Next, let’s get this output into openhab. So, let’s configure the exec binding to start the script every minute:

/etc/openhab2/things/exec.things

exec:command:semsportal_json [command="bash /etc/openhab2/scripts/semsapi.sh",interval=60, timeout=10, autorun=false]

/etc/openhab2/items/goodwe.items

String PV_JSON_Out "[%s]" {channel="exec:command:semsportal_json:output"}

Here are some example values I read from my Inverter. Adjust to you needs:

/etc/openhab2/items/goodwe.items

Number:Energy   UG_PV_Total_Output              "Total Output [%.1f kWh]"                   <energy>                                
Number:Energy   UG_PV_Grid                      "Grid [%.1f W]"                             <energy>                                                                                             
Number:Energy   UG_PV_Output_Power              "Output Power [%.1f W]"                     <energy>                                
Number:Energy   UG_PV_Home_Load                 "Home Load [%.1f W]"                        <energy>                                
Number:Energy   UG_PV_Todays_Output             "Today's Output [%.1f kWh]"                 <energy>                                
Number:Energy   UG_PV_Battery                   "Battery Output [%.1f W]"                   <energy>                                
Number          UG_PV_SoC                       "Battery SoC [%d %%]"                       <battery>

And this is the rule that does all the magic in openhab, put this in a rule file, e.g.
/etc/openhab2/rules/goodwe.rules

rule "PV JSON transform"
when 
    Item PV_JSON_Out changed 
then
    val totalOutput = transform("JSONPATH","$.inverter[0].etotal",PV_JSON_Out.state.toString)
    val todaysOutput = transform("JSONPATH","$.inverter[0].eday",PV_JSON_Out.state.toString)
    val grid = transform("JSONPATH","$.powerflow.grid",PV_JSON_Out.state.toString)
    val outputPower = transform("JSONPATH","$.powerflow.pv",PV_JSON_Out.state.toString)
    val homeLoad = transform("JSONPATH","$.powerflow.load",PV_JSON_Out.state.toString)
    val battery = transform("JSONPATH","$.powerflow.bettery",PV_JSON_Out.state.toString)
    val soc = transform("JSONPATH","$.powerflow.soc",PV_JSON_Out.state.toString)

    UG_PV_Total_Output.postUpdate(totalOutput)
    UG_PV_Todays_Output.postUpdate(todaysOutput)
    UG_PV_Grid.postUpdate(grid.toString.replace('(',' ').replace(')',''))
    UG_PV_Output_Power.postUpdate(outputPower.toString.replace('(',' ').replace(')','')) 
    UG_PV_Home_Load.postUpdate(homeLoad.replace('(',' ').replace(')',''))
    UG_PV_Battery.postUpdate(battery.replace('(',' ').replace(')',''))
    UG_PV_SoC.postUpdate(soc)
end

That’s all. Let me know, if you got it working or need some help. Happy to see your ideas on what to do with all the information.

  • RogerG
4 Likes

hey rogerG ,

Some people are very new to openhab , like me :slight_smile: i would love to implement my solar panels again . in the past, before they changed to the sems portal this was very easy . But i dont understand anything with the shell scripting. i need a little more step by step detail how to implement this :confused:

thanks in advance.

Thank you for the description @RogerG007 this is super helpful!

I think I have everything in place but for some reason I get an error that the exec cannot find the script file. I have tried changing the exec.things to match my directory but it still throws an error. Can you help me with how I point out where my script is or do I have it in the wrong place? I’m running a windows machine and have it under C:\openhab\conf\scripts

Hi Johan,

the problem is the windows machine. I’m sorry but the shell script won’t work with windows that easily.

It should be possible to get it running though, but you need either the cURL or cygWin (which includes cURL). Both available for free and I believe there are already some discussions around these tools in the forum.

Nevertheless let me see if I can provide you an alternative script for Windows, it shouldn’t be too difficult. However I can’t really test it in full, as I don’t have openhab running on Windows.

I’ll come back to you asap.

RogerG

Ok, I figured it was related to that.

Would be super nice if you want to help with alternative. I’m of course happy to test!

Hi Roger,

I’m using Linux Mint as an OS and when I try to run the script to test if it works it tells me that it has an

./semsapi.sh: line 6: unexpected EOF while looking for matching `" ’
./semsapi.sh: line 7: syntax error: unexpected end of file

This happens with my username and password and also the exact copy of the lines on this post.

I haven’t been able to find what can possible be wrong about the lines

Below my screenshot of the output

Also, where do you put the rules? beneath the Number:Energy in the same goodwe.items?

I have followd the steps, bur nothing hapens in the paperui.

I think my goodwe.items is not correct.

String PV_JSON_Out "[%s]" {channel="exec:command:semsportal_json:output"}

Number:Energy   UG_PV_Total_Output              "Total Output [%.1f kWh]"                   <energy>                                
Number:Energy   UG_PV_Grid                      "Grid [%.1f W]"                             <energy>                                                                                             
Number:Energy   UG_PV_Output_Power              "Output Power [%.1f W]"                     <energy>                                
Number:Energy   UG_PV_Home_Load                 "Home Load [%.1f W]"                        <energy>                                
Number:Energy   UG_PV_Todays_Output             "Today's Output [%.1f kWh]"                 <energy>                                
Number:Energy   UG_PV_Battery                   "Battery Output [%.1f W]"                   <energy>                                
Number          UG_PV_SoC                       "Battery SoC [%d %%]"                       <battery>

rule "PV JSON transform"
when 
    Item PV_JSON_Out changed 
then
    val totalOutput = transform("JSONPATH","$.inverter[0].etotal",PV_JSON_Out.state.toString)
    val todaysOutput = transform("JSONPATH","$.inverter[0].eday",PV_JSON_Out.state.toString)
    val grid = transform("JSONPATH","$.powerflow.grid",PV_JSON_Out.state.toString)
    val outputPower = transform("JSONPATH","$.powerflow.pv",PV_JSON_Out.state.toString)
    val homeLoad = transform("JSONPATH","$.powerflow.load",PV_JSON_Out.state.toString)
    val battery = transform("JSONPATH","$.powerflow.bettery",PV_JSON_Out.state.toString)
    val soc = transform("JSONPATH","$.powerflow.soc",PV_JSON_Out.state.toString)

    UG_PV_Total_Output.postUpdate(totalOutput)
    UG_PV_Todays_Output.postUpdate(todaysOutput)
    UG_PV_Grid.postUpdate(grid.toString.replace('(',' ').replace(')',''))
    UG_PV_Output_Power.postUpdate(outputPower.toString.replace('(',' ').replace(')','')) 
    UG_PV_Home_Load.postUpdate(homeLoad.replace('(',' ').replace(')',''))
    UG_PV_Battery.postUpdate(battery.replace('(',' ').replace(')',''))
    UG_PV_SoC.postUpdate(soc)
end

Where am I going wrong?

Hi Patrick,

you need to put the rule-bit of code not in the goodwe.items file but into a goodwe.rules file (/etc/openhab2/rules/goodwe.rules).
I’ll update my post above to make that more clear.

Hi Jos,

I’m really sorry I missed your post and questions.

You need to put your rules in a rule file, e.g. /etc/openhab2/rules/goodwe.rules.

And you are right there was a copy & paste problem with my script using the wrong quotation marks. I’ve updated the script above and you should not have that error anymore.

RogerG007

hello, i tryied your guide, but im doing something wrong…
/etc/openhab2/scripts/semsapi.sh a copied your script into file and updated login, password and id.
then i bind excec binding and json.
then i tryied run the script and i have few errors - below.
in the exec bind i have settings:
command: bash /etc/openhab2/scripts/semsapi.sh
transform: REGEX((.*))

where could be mistake?
thanks a lot, a im new in OH, just trying, that is good platform for me…everythings work fine, but i dont understant this not so much…
/etc/openhab2/scripts/semsapi.sh: line 2: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 3: curl: command not found /etc/openhab2/scripts/semsapi.sh: line 4: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 5: jq: command not found /etc/openhab2/scripts/semsapi.sh: line 6: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 7: curl: command not found /etc/openhab2/scripts/semsapi.sh: line 8: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 9: jq: command not found /etc/openhab2/scripts/semsapi.sh: line 2: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 3: curl: command not found /etc/openhab2/scripts/semsapi.sh: line 4: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 5: jq: command not found /etc/openhab2/scripts/semsapi.sh: line 6: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 7: curl: command not found /etc/openhab2/scripts/semsapi.sh: line 8: $’\r’: command not found /etc/openhab2/scripts/semsapi.sh: line 9: jq: command not found

Hello Roger,
I have not been able to get this to work.
Added the .sh script and updated the credentials but running the script does not show me any output apart from ‘null’.

[14:32:04] openhabian@openhab:/etc/openhab2/scripts$ bash semsapi.sh
null

Hey Rudi,
I’ve got the same problem, did you find a solution?

Hi Toine,

I think I found the problem. For some reason adding code to this forum messed up the quotation marks in the shell script. I edited the original post to correct this.

You can also try the following command on shell level and should get a response now. Remember adding your account and password.

curl https://www.semsportal.com/api/v2/Common/CrossLogin --silent --header "Content-Type: application/json" --header "Connect: keep-alive" --header "User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)" --header "Accept-Language: en;q=1" --header "Token: {\"version\":\"v2.1.0\",\"client\":\"ios\",\"language\":\"en\"}" --data-binary "{\"account\":\"secondopenhabuser@gmail.com\",\"pwd\":\"yourpassword\"}"

RogerG

Hey Roger,

Got this output

“hasError”: false,
“code”: 0,
“msg”: “Success”,
“data”: {
“agreement_agreement”: true
},
“components”: {
“para”: null,
“langVer”: 87,
“timeSpan”: 0,
“api”: “http://eu.semsportal.com:82/api/Auth/GetTokenV2”,
“msgSocketAdr”: “https://eu-xxzx.semsportal.com
},
“api”: “https://eu.semsportal.com/api/

Thanks in advance for your reply.

Hey Roger
For testing I ran this script (with my credentials) in bash.

’ RESULT=$(curl https://www.semsportal.com/api/v2/Common/CrossLogin --silent --header “Content-Type: application/json” --header “Connect: keep-alive” --header “User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)” --header “Accept-Language: en;q=1” --header “Token: {“version”:“v2.1.0”,“client”:“ios”,“language”:“en”}” --data-binary “{“account":"me@gmail.com”,“pwd”:“passwrd”}”)

HEADER=$(echo “$RESULT”|jq “.data”|tr ‘\n’ ’ ')

echo $HEADER

RETURN=$(curl https://www.semsportal.com/api/v2/PowerStation/GetMonitorDetailByPowerstationId --silent --header “Content-Type: application/json” --header “Accept: /” --header “User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)” --header “Accept-Language: en;q=1” --header 'Token: '"$HEADER" --data-binary “{“powerStationId”:“myID”}”)

echo “$RETURN” | jq “.data” ’

And got this output:

{ “agreement_agreement”: true }
parse error: Invalid numeric literal at line 1, column 7

1st line is output for “echo $HEADER”, so far so good.
There must be a typo in the second part of the script I guess!

Hi Toine,

I can’t find any differences in my running script and the one I’ve uploaded here.

Your test script seems not to respond with the correct HEADER. It should return something like this:

{ "uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "timestamp": 1606820035659, "token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "client": "ios", "version": "v2.1.0", "language": "en" }

This is needed for authentication in the second curl command.

RogerG

Hi Toine,

I’ve updated my script with variables for username, password and power station. This is the only difference between my working script and the one above. Could you give this a try:

#!/bin/bash

USERNAME="me@mail.com"
PASSWORD=“MYPASSWORD”
POWERSTATION=“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”

RESULT=$(curl https://www.semsportal.com/api/v2/Common/CrossLogin --silent --header “Content-Type: application/json” --header “Connect: keep-alive” --header “User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)” --header “Accept-Language: en;q=1” --header “Token: {“version”:“v2.1.0”,“client”:“ios”,“language”:“en”}” --data-binary “{“account”:”$USERNAME",“pwd”:"$PASSWORD"}")

HEADER=$(echo “$RESULT”|jq “.data”|tr ‘\n’ ’ ')

RETURN=$(curl https://www.semsportal.com/api/v2/PowerStation/GetMonitorDetailByPowerstationId --silent --header “Content-Type: application/json” --header “Accept: /” --header “User-Agent: PVMaster/2.1.0 (iPhone; iOS 13.0; Scale/2.00)” --header “Accept-Language: DE;q=1” --header 'Token: '"$HEADER" --data-binary “{“powerStationId”:”$POWERSTATION"}")

echo “$RETURN” | jq “.data”

I’ve also added my shell-script as a file. You need to rename it of course to semsapi.sh and make it executable.

semsapi.txt (950 Bytes)

Hope this helps.

RogerG

Hey Roger,
If I run your shell script with my credentials I get this result: 请求参数:
Putting this result in an internet translation machine learns me it means: “aanvraagparameters: [nl]”
Dutch parameters, which is good, but after the “:” no parameters!!
I’ll give it a rest for now. Maybe this weekend I’ll give it another try!
Thanks.

Maybe you need to change the “Accept-Language: DE” in my script to “Accept-Language: NL”. Just a quick idea coming to mind.

RogerG

Hey Roger,

The script works fine! Turned out that you have to log in Sems Portal with the new created user to initiate him. I made the 2e user for the script but didn’t use him in the Portal.
Learning all the time! (-;
I’m going to implement it in OpenHAB now.
Thanks for your time and help!

Toine.

1 Like