Skip to content

Instantly share code, notes, and snippets.

@SplinterII
Last active May 1, 2024 07:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save SplinterII/dbd6621ce479194571fde919dcb172d1 to your computer and use it in GitHub Desktop.
Save SplinterII/dbd6621ce479194571fde919dcb172d1 to your computer and use it in GitHub Desktop.
Goodwe SEMS portal Solar panel data to Node-RED

** THIS NODE IS NO LONGER MAINTAINED **

UPDATE 1 nov 2021: Mind the power station ID!

The Goodwe PV solar panel inverters come with a webportal which show all it's performance. This Node-RED flow outputs a (small) part of the data available from the Goodwe API. It's using the version 2 API.

Outputs from this flow include:

  • fysical address of the converter,
  • name of the station,
  • total power generated and
  • power generated today.

Much more data is readily available.

In the fuction nodes the flow needs these inputs:

  • The loginname to the portal,
  • The corresponding password (both set in the "Get ID token"-function node).
  • The power station ID, as found in the URL when logged into the portal.

The power station ID is set in the "Get station output"-function node. To find the ID for your installation, login to the normal SEMS webportal. Once logged in, the last part of the URL in your webbrowser is the ID. Copy this part into the script for the variable "psid".

Among other sources, this helped most: DiedB/Homey-SolarPanels#28 (comment)

The first HTTP API call returns a session login token ID. This token is used in the second call, which returns all data. It seems the word "token" is used differently throughout the API, which can be confusing. The token is session-bound and expires in ? time. Replies from the "Request station" call can be delayed upto 10-ish seconds.

[
{
"id": "8d9517ae094a5e8f",
"type": "tab",
"label": "Query SEMS portal for Goodwe Inverter",
"disabled": false,
"info": ""
},
{
"id": "7666019830c94009",
"type": "debug",
"z": "8d9517ae094a5e8f",
"name": "Token Details",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 740,
"y": 200,
"wires": []
},
{
"id": "35b7837b9e219f92",
"type": "http request",
"z": "8d9517ae094a5e8f",
"name": "Request token",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"x": 480,
"y": 240,
"wires": [
[
"7666019830c94009",
"2f2802572b0a16fa"
]
]
},
{
"id": "20f12cc0ec2b10df",
"type": "function",
"z": "8d9517ae094a5e8f",
"name": "Get ID token from login",
"func": "// Credentials as used for login to the Semsportal at https://www.semsportal.com/\n\naccount = \"YOUR_SEMS_USERNAME\";\npwd = \"YOUR_SEMS_PASSWORD\";\n\n// ---------- no changes needed below ----------------------------\nloginPayload = { 'account': account, 'pwd': pwd };\n\n// It's a given, likely from the API documentation?\ntoken = '{\"client\": \"ios\", \"version\": \"v2.1.0\", \"language\": \"en\"}';\n\nglobal_url = 'https://www.semsportal.com/api/';\nheaders = { 'token': token };\n\nmsg.url = global_url + \"v2/Common/CrossLogin\";\nmsg.headers = headers;\nmsg.payload = loginPayload;\nreturn msg;\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 430,
"y": 180,
"wires": [
[
"35b7837b9e219f92"
]
]
},
{
"id": "6b960b40ef6d9b5c",
"type": "inject",
"z": "8d9517ae094a5e8f",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 220,
"y": 180,
"wires": [
[
"20f12cc0ec2b10df"
]
]
},
{
"id": "2f2802572b0a16fa",
"type": "function",
"z": "8d9517ae094a5e8f",
"name": "Get station output",
"func": "if(msg==\"no response from server\") { return; }\n\n// Power station ID can be found in the last part of the URL of the SEMSportlal, once logged in\n\n// Power station ID can be found in the last part of the URL of the SEMSportlal, once logged in\npsid = 'd0eac052-96....GET YOUR OWN CODE HERE....d0';\n\n\n// -------------- Generic below ---------------------------------\n\n// https://github.com/DiedB/Homey-SolarPanels/issues/28\n\n// Get the relevant response from the login call: \nvar uid = msg.payload.data.uid; // User ID\nvar id_token = msg.payload.data.token; // Time-bound token from login\nvar timestamp = msg.payload.data.timestamp;\n\n// Warning: The API uses 'token' with different meanings throughout the communication\ntoken = '{\"uid\": \"'+ uid + '\", \"timestamp\": '+ timestamp+ ', \"token\": \"'+ id_token + '\", \"client\": \"ios\", \"version\": \"v2.0.4\", \"language\": \"en\"}';\nheaders = { 'User-Agent': 'JH', 'Token': token }\n\nglobal_url = 'https://www.semsportal.com/api/'\nmsg.url = global_url + \"v2/PowerStation/GetMonitorDetailByPowerstationId\";\n\nmsg.headers = headers;\n\npayload = {'powerStationId': psid};\nmsg.payload = payload;\n\nreturn msg;\n\n",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 570,
"y": 300,
"wires": [
[
"c63f21c5f355ef7a",
"abc27ec229e67dd6"
]
]
},
{
"id": "ed7ace700ba8677a",
"type": "debug",
"z": "8d9517ae094a5e8f",
"name": "Data",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 890,
"y": 420,
"wires": []
},
{
"id": "c63f21c5f355ef7a",
"type": "http request",
"z": "8d9517ae094a5e8f",
"name": "Request station",
"method": "POST",
"ret": "obj",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"authType": "",
"x": 620,
"y": 360,
"wires": [
[
"ebf19d5d3f71fc32",
"f6c3c5bea5812317",
"98187240f169989f"
]
]
},
{
"id": "ebf19d5d3f71fc32",
"type": "function",
"z": "8d9517ae094a5e8f",
"name": "Get values",
"func": "// Get values as shown in the webportal:\n\n// Stationname, top left as displayed in the dropdown (not sure how more stations handle....?)\nvar station_name = msg.payload.data.info.stationname;\n\n// Fysical address\nvar address = msg.payload.data.info.address;\n\n// Total power als shown on the webportal left below today's production\nvar total_power = msg.payload.data.kpi.total_power;\n\n// Today's power als in the circle middle down the webportal main page\nvar day_power = msg.payload.data.kpi.power;\n\nmsg = {};\n\nmsg.payload = {'station_name': station_name, 'address': address, 'total_power': total_power, 'day_power': day_power};\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 710,
"y": 420,
"wires": [
[
"ed7ace700ba8677a"
]
]
},
{
"id": "f6c3c5bea5812317",
"type": "debug",
"z": "8d9517ae094a5e8f",
"name": "Station Info",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 830,
"y": 360,
"wires": []
},
{
"id": "98187240f169989f",
"type": "change",
"z": "8d9517ae094a5e8f",
"name": "",
"rules": [
{
"t": "set",
"p": "payload",
"pt": "msg",
"to": "payload.data.inverter[0].d",
"tot": "msg"
}
],
"action": "",
"property": "",
"from": "",
"to": "",
"reg": false,
"x": 840,
"y": 300,
"wires": [
[
"a962b60aa198dfb0"
]
]
},
{
"id": "a962b60aa198dfb0",
"type": "debug",
"z": "8d9517ae094a5e8f",
"name": "Data",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 990,
"y": 300,
"wires": []
},
{
"id": "abc27ec229e67dd6",
"type": "debug",
"z": "8d9517ae094a5e8f",
"name": "Token Details",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 760,
"y": 240,
"wires": []
}
]
@Tinchen9b
Copy link

Hello,
Since few days I am receiving;

JSON parse error
TypeError: Cannot read property 'info' of undefined

anyone else as well?

@SplinterII
Copy link
Author

SplinterII commented Dec 2, 2022

Sorry, no clue, I'm not using the code anymore at all. But it does seem to me like a change in the API. Also at this point no clue where to find that....

Edit: ok, here we are: http://www.goodwe-power.com:82/swagger/ui/index and select version 3. My code was for version 2, which might be broken now in some way. No clue whether v3 is live since only a few days...

@Tinchen9b
Copy link

Tinchen9b commented Dec 4, 2022

Add

Change this line to :
global_url = 'http://www.goodwe-power.com:82/api/';
and in the node: "Request Token" the return to : "a parsed JSON object"
image

and it should work again.
.. at least mine is working again :)

Thank you for pointing me to the api documentation. 👍

@Hansi3
Copy link

Hansi3 commented Apr 30, 2024

Hi Splinter, thanks for this flow. It works fine for me and in conjunction with this mqtt thing I can know see the power output of my goodwe inverter in my victron controller.
However I only manage to get total AC power (kpi.pac). I would like te have more data like voltage and current for L1, L2, L3.
Is there a list of parameters and corresponding names I could look up to retrieve more data?

Thanks!

@SplinterII
Copy link
Author

Hi Splinter, thanks for this flow. It works fine for me and in conjunction with this mqtt thing I can know see the power output of my goodwe inverter in my victron controller. However I only manage to get total AC power (kpi.pac). I would like te have more data like voltage and current for L1, L2, L3. Is there a list of parameters and corresponding names I could look up to retrieve more data?

Thanks!

Hi Hansi, I don't have this inverter anymore and do not have the code running already for some time. There must indeed be a list of all parameters available through the API, but at this point I wouldn't know where. I'm using Home Assistant now, and just saw there's an integration for GoodWe into Home Assistant. Maybe you can find clues there? https://github.com/TimSoethout/goodwe-sems-home-assistant
Goodluck!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment