Logic Machine Forum
Goodwe - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Scripting (https://forum.logicmachine.net/forumdisplay.php?fid=8)
+--- Thread: Goodwe (/showthread.php?tid=4286)



Goodwe - Danny - 06.10.2022

Dear readers,

I am looking for the api of Goodwe inverters (Semsportal)
on Github I found the below but I don't know how to integrate this into the W4K.

Can someone help me with this?

thanks in advance


Code:
#####User details here#####
##Givenergy Portal API Key Goes Below between " "


email = "username@email.com"
password = "password_here"
Powerstation_Id = "Portal_ID_here"


########end user input#############


##Login to SEMS API ##
headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
headers.Add("sec-ch-ua", "`" Not A;Brand`";v=`"99`", `"Chromium`";v=`"99`", `"Microsoft Edge`";v=`"99`"")
headers.Add("Accept", "application/json, text/javascript, */*; q=0.01")
headers.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
headers.Add("X-Requested-With", "XMLHttpRequest")
headers.Add("sec-ch-ua-mobile", "?0")
headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39")
headers.Add("sec-ch-ua-platform", "`"Windows`"")
headers.Add("Origin", "https://eu.semsportal.com")
headers.Add("Sec-Fetch-Site", "same-origin")
headers.Add("Sec-Fetch-Mode", "cors")
headers.Add("Sec-Fetch-Dest", "empty")
headers.Add("Referer", "https://eu.semsportal.com/home/login")
headers.Add("Accept-Language", "en-GB,en;q=0.9,en-US;q=0.8")

body = "account=$email&pwd=$password&code="

response = Invoke-WebRequest 'https://eu.semsportal.com/Home/Login' -Method 'POST' -Headers $headers -Body $body -SessionVariable session
response.Content | Out-File -FilePath.\Login.txt
session | Out-File -FilePath.\Session.txt
Write-Output "Login Response Saved to: Login.txt"


headers2 = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
headers2.Add("sec-ch-ua", "`" Not A;Brand`";v=`"99`", `"Chromium`";v=`"99`", `"Microsoft Edge`";v=`"99`"")
headers2.Add("Accept", "*/*")
headers2.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
headers2.Add("X-Requested-With", "XMLHttpRequest")
headers2.Add("sec-ch-ua-mobile", "?0")
headers2.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39")
headers2.Add("sec-ch-ua-platform", "`"Windows`"")
headers2.Add("Origin", "https://eu.semsportal.com")
headers2.Add("Sec-Fetch-Site", "same-origin")
headers2.Add("Sec-Fetch-Mode", "cors")
headers2.Add("Sec-Fetch-Dest", "empty")
headers2.Add("Referer", "https://eu.semsportal.com/PowerStation/PowerStatusSnMin/$Powerstation_Id")
headers2.Add("Accept-Language", "en-GB,en;q=0.9,en-US;q=0.8")

body2 = "str=%7B%22api%22%3A%22v1%2FPowerStation%2FGetMonitorDetailByPowerstationId%22%2C%22param%22%3A%7B%22powerStationId%22%3A%22$Powerstation_Id%22%7D%7D"

response2 = Invoke-RestMethod 'https://eu.semsportal.com/GopsApi/Post?s=v1/PowerStation/GetMonitorDetailByPowerstationId' -Method 'POST' -Headers $headers2 -Body $body2 -WebSession $session
response2.data.inverter[0].invert_full | ConvertTo-Json -Depth 10 | Out-File -FilePath .\Data\GoodweData.txt -Encoding ASCII

Write-Output "Data Saved to: GoodweData.txt"
Write-Output "All done - Closing Powershell in 5...."
start-sleep -s 5

Exit



RE: Goodwe - admin - 07.10.2022

Use this as a starting point:
Code:
require('socket.http')
require('ltn12')
require('json')

email = "username@email.com"
password = "password_here"
powerstation_id = "Portal_ID_here"

function encodepost(t)
  local res = {}
  local esc = require('socket.url').escape

  for k, v in pairs(t) do
    res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
  end

  return table.concat(res, '&')
end

body = encodepost({
  account = email,
  pwd = password,
  code = "",
})

headers = {
  ["sec-ch-ua"] = "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Microsoft Edge\";v=\"99\"",
  ["Accept"] = "application/json, text/javascript, */*; q=0.01",
  ["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8",
  ["X-Requested-With"] = "XMLHttpRequest",
  ["sec-ch-ua-mobile"] = "?0",
  ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39",
  ["sec-ch-ua-platform"] = "\"Windows\"",
  ["Origin"] = "https://eu.semsportal.com",
  ["Sec-Fetch-Site"] = "same-origin",
  ["Sec-Fetch-Mode"] = "cors",
  ["Sec-Fetch-Dest"] = "empty",
  ["Referer"] = "https://eu.semsportal.com/home/login",
  ["Accept-Language"] = "en-GB,en;q=0.9,en-US;q=0.8",
  ["Content-Length"] = #body,
}

response = {}

res, code, hdrs, stat = socket.http.request({
  url = "https://eu.semsportal.com/Home/Login",
  method = "POST";
  headers = headers,
  source = ltn12.source.string(body),
  sink = ltn12.sink.table(response),
})

log(res, code, hdrs, stat, response)
Post what you get in Logs tab.


RE: Goodwe - Danny - 07.10.2022

Code:
* arg: 1
  * number: 1
* arg: 2
  * number: 200
* arg: 3
  * table:
   ["server"]
    * string: nginx
   ["content-type"]
    * string: application/json; charset=utf-8
   ["connection"]
    * string: close
   ["content-length"]
    * string: 109
   ["cache-control"]
    * string: private
   ["x-aspnetmvc-version"]
    * string: 5.2
   ["set-cookie"]
    * string: ASP.NET_SessionId=pltv2ppqqnlhzlhalnlmh3gu; path=/; HttpOnly; SameSite=Lax, ASP.NET_SessionId=pltv2ppqqnlhzlhalnlmh3gu; path=/; HttpOnly; SameSite=Lax, language=en; path=/
   ["x-powered-by"]
    * string: ASP.NET
   ["x-aspnet-version"]
    * string: 4.0.30319
   ["date"]
    * string: Fri, 07 Oct 2022 06:57:38 GMT
* arg: 4
  * string: HTTP/1.1 200 OK
* arg: 5
  * table:
   [1]
    * string: {"code":0,"msg":"","data":{"redirect":"/PowerStation/PowerStatusSnMin/--Inverter id--"}}
 Here the log tab,
Thanks admin
how do i get the power logs from the inverter?


RE: Goodwe - admin - 07.10.2022

Try this:
Code:
require('socket.http')
require('ltn12')
require('json')

email = "username@email.com"
password = "password_here"
powerstation_id = "Portal_ID_here"

function encodepost(t)
  local res = {}
  local esc = require('socket.url').escape

  for k, v in pairs(t) do
    res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
  end

  return table.concat(res, '&')
end

body = encodepost({
  account = email,
  pwd = password,
  code = "",
})

headers = {
  ["sec-ch-ua"] = "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Microsoft Edge\";v=\"99\"",
  ["Accept"] = "application/json, text/javascript, */*; q=0.01",
  ["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8",
  ["X-Requested-With"] = "XMLHttpRequest",
  ["sec-ch-ua-mobile"] = "?0",
  ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39",
  ["sec-ch-ua-platform"] = "\"Windows\"",
  ["Origin"] = "https://eu.semsportal.com",
  ["Sec-Fetch-Site"] = "same-origin",
  ["Sec-Fetch-Mode"] = "cors",
  ["Sec-Fetch-Dest"] = "empty",
  ["Referer"] = "https://eu.semsportal.com/home/login",
  ["Accept-Language"] = "en-GB,en;q=0.9,en-US;q=0.8",
  ["Content-Length"] = #body,
}

response = {}

res, code, hdrs, stat = socket.http.request({
  url = "https://eu.semsportal.com/Home/Login",
  method = "POST",
  headers = headers,
  source = ltn12.source.string(body),
  sink = ltn12.sink.table(response),
})

cookie = hdrs['set-cookie']
if cookie then
  headers["Cookie"] = cookie:split(';')[1]
end

headers["Referer"] = "https://eu.semsportal.com/PowerStation/PowerStatusSnMin/" .. powerstation_id

log(res, code, hdrs, stat, response)

body = encodepost({
  str = json.encode({
    api = "v1/PowerStation/GetMonitorDetailByPowerstationId",
    param = {
      powerStationId = powerstation_id
    }
  })
})

response = {}

res, code, hdrs, stat = socket.http.request({
  url = "https://eu.semsportal.com/GopsApi/Post?s=v1/PowerStation/GetMonitorDetailByPowerstationId",
  method = "POST",
  headers = headers,
  source = ltn12.source.string(body),
  sink = ltn12.sink.table(response),
})

log(res, code, hdrs, stat, response)



RE: Goodwe - Danny - 07.10.2022

Code:
* arg: 1
  * number: 1
* arg: 2
  * number: 200
* arg: 3
  * table:
   ["server"]
    * string: nginx
   ["content-type"]
    * string: application/json; charset=utf-8
   ["connection"]
    * string: close
   ["content-length"]
    * string: 55
   ["cache-control"]
    * string: private
   ["x-aspnetmvc-version"]
    * string: 5.2
   ["date"]
    * string: Fri, 07 Oct 2022 10:32:21 GMT
   ["x-powered-by"]
    * string: ASP.NET
   ["x-aspnet-version"]
    * string: 4.0.30319
* arg: 4
  * string: HTTP/1.1 200 OK
* arg: 5
  * table:
   [1]
    * string: {"code":-100,"msg":"Network failure, please try again"}



RE: Goodwe - admin - 07.10.2022

Can you send your credentials via PM?


RE: Goodwe - admin - 10.10.2022

Try this:
Code:
require('socket.http')
require('ltn12')
require('json')

email = "..."
password = "..."
powerstation_id = "..."

function encodepost(t)
  local res = {}
  local esc = require('socket.url').escape

  for k, v in pairs(t) do
    res[ #res + 1 ] = esc(k) .. '=' .. esc(v)
  end

  return table.concat(res, '&')
end

body = json.encode({
  account = email,
  pwd = password,
  is_local = True,
  agreement_agreement = 1
})

token = json.encode({
  uid = "",
  timestamp = 0,
  token = "",
  client = "web",
  version = "",
  language = "en-GB",
})

headers = {
  ["Content-Type"] = "application/json; charset=utf-8",
  ["Accept"] = "Content-Type: application/json; charset=UTF-8",
  ["Content-Length"] = #body,
  ["Accept"] = "application/json",
  ["token"] = token,
}

response = {}

res, code, hdrs, stat = socket.http.request({
  url = "https://eu.semsportal.com/api/v2/Common/CrossLogin",
  method = "POST",
  headers = headers,
  source = ltn12.source.string(body),
  sink = ltn12.sink.table(response),
})

if res and code == 200 then
  response = json.decode(table.concat(response))
  headers.token = json.encode(response.data)

  url = response.api .. "/v2/PowerStation/GetMonitorDetailByPowerstationId"

  response = {}

  body = json.encode({ powerStationId = powerstation_id })
  headers["Content-Length"] = #body

  res, code, hdrs, stat = socket.http.request({
    url = url,
    method = "POST",
    headers = headers,
    source = ltn12.source.string(body),
    sink = ltn12.sink.table(response),
  })

  response = json.decode(table.concat(response))
  invert_full = response.data.inverter[1].invert_full

  log(invert_full)
end



RE: Goodwe - Danny - 10.10.2022

Admin this works perfectly thanks
I will continue with this.


RE: Goodwe - pioneersteffen - 07.01.2023

(10.10.2022, 12:10)Danny Wrote: Admin this works perfectly thanks
I will continue with this.

@Danny: What is your experience with the script? Is it working? Have you made further modifications?

Many thanks!


RE: Goodwe - pioneersteffen - 04.06.2023

Hi @all,

Many thanks for the script it works perfectly, I getting a response as following:

Code:
SEMS 04.06.2023 12:49:42
* table:
["backUpVload_S"]
  * number: 0
["istr10"]
  * number: 670
["battary_work_mode"]
  * number: 0
["etotal"]
  * number: 61.6
["pbackup"]
  * number: 0
["istr3"]
  * number: 0
["istr15"]
  * number: -810.4
["bms_alarm"]
  * number: 0
["pre_hour_lasttotal"]
  * userdata: NULL
["bms_discharge_i_max"]
  * userdata: NULL
["sn"]
  * string: 56000DTS229R0076
["isbuettey"]
  * bool: false
["backUpPload_T"]
  * number: 0
["istr14"]
  * number: 185.2
["total_buy"]
  * number: 0
["ram"]
  * number: 53
["dataloggersn"]
  * string:
["genset_etotal"]
  * userdata: NULL
["istr16"]
  * number: 560.4
["genset_mode"]
  * userdata: NULL
["iac2"]
  * number: 5
["battery8sn"]
  * userdata: NULL
["reactive_power"]
  * number: 0
["name"]
  * string: SDT G2
["ezPro_connect_status"]
  * userdata: NULL
["backUpIload_S"]
  * number: 0
["mtActivepowerR"]
  * number: 0
["fac3"]
  * number: 50.01
["meter_type"]
  * userdata: NULL
["istr13"]
  * number: 220
["pac"]
  * number: 3457
["vpv2"]
  * number: 360.2
["mtActivepowerT"]
  * number: 0
["cts"]
  * userdata: NULL
["tempperature"]
  * number: 56.3
["eDischargeDay"]
  * number: 0
["ibattery1"]
  * number: 0
["istr6"]
  * number: 0.1
["istr8"]
  * number: 118
["istr1"]
  * number: 5.1
["yesterdaysellertotal"]
  * number: 0
["more_batterys"]
  * userdata: NULL
["outputpower"]
  * number: 3457
["mtActivepowerS"]
  * number: 0
["eTotalBuy"]
  * userdata: NULL
["master"]
  * userdata: NULL
["powerstation_id"]
  * string: fe648d7b-b84b-4427-bb5f-f4b4a5c4c17b
["istr4"]
  * number: 0
["istr7"]
  * number: 64.7
["ipv2"]
  * number: 5
["eP_connect_status_recover"]
  * userdata: NULL
["isbuetteybpu"]
  * bool: false
["buy"]
  * number: 0
["last_time"]
  * number: 1685875672648
["ipv4"]
  * number: 0
["genset_yeasterdayTotal"]
  * userdata: NULL
["iload"]
  * number: 0
["isESUOREMU"]
  * bool: false
["backUpPload_S"]
  * number: 0
["change_type"]
  * number: 0
["backUpVload_T"]
  * number: 0
["meterConnectStatus"]
  * userdata: NULL
["current_hour_pv"]
  * number: 0
["battery3sn"]
  * userdata: NULL
["pre_hour_time"]
  * userdata: NULL
["thismonthetotle"]
  * number: 43.1
["hasmeter"]
  * bool: false
["bms_warning"]
  * number: 0
["battery4sn"]
  * userdata: NULL
["yesterdayct2sellertotal"]
  * userdata: NULL
["eBatteryCharge"]
  * userdata: NULL
["pmeter"]
  * number: 0
["hour_total"]
  * number: 26
["istr9"]
  * number: 669.3
["iac3"]
  * number: 5
["battery5sn"]
  * userdata: NULL
["soh"]
  * number: 0
["isbuetteybps"]
  * bool: false
["all_eday"]
  * number: 18.5
["iac1"]
  * number: 5.1
["turnon_time"]
  * number: 1685712233090
["battery1sn"]
  * userdata: NULL
["equipment_name"]
  * userdata: NULL
["battery6sn"]
  * userdata: NULL
["vac1"]
  * number: 226.3
["vac3"]
  * number: 224.2
["genset_power"]
  * userdata: NULL
["status"]
  * number: 1
["itotal"]
  * number: 7.392
["battery_count"]
  * userdata: NULL
["total_pbattery"]
  * number: 0
["workmode"]
  * number: 1
["inverter_type"]
  * number: 0
["parallel_code"]
  * userdata: NULL
["vpv3"]
  * number: 0
["vpv1"]
  * number: 346
["eBatteryDischarge"]
  * userdata: NULL
["deratingMode"]
  * userdata: NULL
["safetyConutry"]
  * number: 79
["errors"]
  * table:
["extend_properties"]
  * userdata: NULL
["istr5"]
  * number: 0.1
["genset_eday"]
  * number: 0
["check_code"]
  * string: 045109
["isoLimit"]
  * number: 0
["eday"]
  * number: 18.5
["eDayBuy"]
  * number: 0
["battery7sn"]
  * userdata: NULL
["yesterdaybuytotal"]
  * number: 0
["model_type"]
  * string: GW6K-DT
["vac2"]
  * number: 225.1
["fault_messge"]
  * number: 0
["soc"]
  * number: 44
["genset_start_mode"]
  * userdata: NULL
["yesterdayetotalload"]
  * number: 0
["yesterdayetotal"]
  * userdata: NULL
["yesterdaylastime"]
  * number: 0
["vbattery1"]
  * number: 0
["bms_charge_i_max"]
  * number: 3
["total_sell"]
  * number: 0
["pv_power"]
  * number: 0
["iday"]
  * number: 2.22
["lastmonthetotle"]
  * number: 0.4
["eChargeDay"]
  * number: 0
["backUpIload_T"]
  * number: 0
["vpv4"]
  * number: 0
["battStrings"]
  * userdata: NULL
["istr12"]
  * number: 220
["seller"]
  * number: 0
["firmwareversion"]
  * number: 1515
["istr11"]
  * number: 0
["istr2"]
  * number: 5
["change_time"]
  * number: 0
["fac1"]
  * number: 50.01
["capacity"]
  * number: 6
["bmssoftwareversion"]
  * userdata: NULL
["ct_solution_type"]
  * number: 0
["leakage_current"]
  * userdata: NULL
["pf"]
  * number: 0
["ipv3"]
  * number: 0
["ipv1"]
  * number: 5.1
["fac2"]
  * number: 50.01
["vload"]
  * number: 0
["battery2sn"]
  * userdata: NULL
["eP_connect_status_happen"]
  * userdata: NULL

How can I pick a variable out of the table? 

For example I want to pick out “pac” and write this value to a group address. How would this work?

Many thanks!

Best Regards 
Steffen


RE: Goodwe - admin - 05.06.2023

Use this:
Code:
grp.checkupdate('1/1/1', invert_full.pac)



RE: Goodwe - pioneersteffen - 10.06.2023

(05.06.2023, 06:13)admin Wrote: Use this:
Code:
grp.checkupdate('1/1/1', invert_full.pac)

Many thanks admin! Works perfectly! :-)


RE: Goodwe - Amelie - 13.06.2023

I have a system with 2 inverters and one batterij, connected to one inverter.

I have copied the code above, but I have the impression that only the data from one inverter is displayed. How can I display the data of the other inverter?


RE: Goodwe - admin - 14.06.2023

Try this for multiple inverters. The order of the returned data might not be constant. So you might have to check the serial number (sn field) invert_full_1 and invert_full_2 tables to distinguish to which inverter the data belongs to.
Code:
invert_full_1 = response.data.inverter[1].invert_full
log(invert_full_1)

invert_full_2 = response.data.inverter[2].invert_full
log(invert_full_2)



RE: Goodwe - Amelie - 17.06.2023

(14.06.2023, 07:02)admin Wrote: Try this for multiple inverters. The order of the returned data might not be constant. So you might have to check the serial number (sn field) invert_full_1 and invert_full_2 tables to distinguish to which inverter the data belongs to.
Code:
invert_full_1 = response.data.inverter[1].invert_full
log(invert_full_1)

invert_full_2 = response.data.inverter[2].invert_full
log(invert_full_2)

I works perfect! Thank you very much.

But, now, I have the error: attempt to concatenate field 'api' (a nil value) stack traceback. The line with causes the error is: 
Code:
url = response.api .. "/v2/PowerStation/GetMonitorDetailByPowerstationId"

A couple of days ago, I didn't have this error. I tried several thing, but I can't find what is causing the error. Can you help?


RE: Goodwe - admin - 19.06.2023

Log the response from the CrossLogin request:
Code:
res, code, hdrs, stat = socket.http.request({
  url = "https://eu.semsportal.com/api/v2/Common/CrossLogin",
  method = "POST",
  headers = headers,
  source = ltn12.source.string(body),
  sink = ltn12.sink.table(response),
})

if res and code == 200 then
  response = json.decode(table.concat(response))

  log(response)

  headers.token = json.encode(response.data)



RE: Goodwe - Amelie - 19.06.2023

I get:

Code:
table:
["msg"]
  * string: error
["data"]
  * table:
   ["timestamp"]
    * number: 1687202887500
   ["uid"]
    * string: 11c54dcf-50cb-48a5-aec2-91b915557029
   ["token"]
    * string: bcbccfa1fa74b141c8d20cad4e7a1d7d
   ["version"]
    * string:
   ["client"]
    * string: web
   ["language"]
    * string: en-GB
["hasError"]
  * bool: false
["api"]
  * string: https://eu.semsportal.com/api/
["components"]
  * table:
   ["langVer"]
    * number: 183
   ["api"]
    * string: http://eu.semsportal.com:82/api/Auth/GetTokenV2
   ["msgSocketAdr"]
    * string: https://eu-xxzx.semsportal.com
   ["timeSpan"]
    * number: 0
   ["para"]
    * userdata: NULL
["code"]
  * number: 0



RE: Goodwe - admin - 20.06.2023

The response looks fine. You can modify this part to log only when api field is missing from the response, then check what the log says:
Code:
if res and code == 200 then
  response = json.decode(table.concat(response))
  headers.token = json.encode(response.data)

  if not response.api then
    log('invalid response', response)
  else
    url = response.api .. "/v2/PowerStation/GetMonitorDetailByPowerstationId"

    response = {}

    body = json.encode({ powerStationId = powerstation_id })
    headers["Content-Length"] = #body

    res, code, hdrs, stat = socket.http.request({
      url = url,
      method = "POST",
      headers = headers,
      source = ltn12.source.string(body),
      sink = ltn12.sink.table(response),
    })

    response = json.decode(table.concat(response))

    -- ...
  end
end



RE: Goodwe - Amelie - 23.06.2023

Thank you very much for your help. 

I still have one question. In the Goodwe app, I can see how many we inject. I want to load in into the LM, so I can ask to the heat pump to make more hot water so it fulfills also the function of a battery. When I send use the following script:

Code:
url = response.api .. "/v2/PowerStation/GetMonitorDetailByPowerstationId"
response = json.decode(table.concat(response))
log (response)
 

The log ends with ..., so I have the impression that not everything is saved in the log and that I can't see everything.

Is there a possibility to saved everything, so that I can see alle the information that the LM is getting back.


RE: Goodwe - admin - 26.06.2023

For testing purposes you can write the results into a file before decoding it:
Code:
response = table.concat(response)
io.writefile('/www/log.json', response)

response = json.decode(response)
-- log(response)

Then open the log in your browser: http://LM_IP/log.json
You can use https://codebeautify.org/jsonviewer to view the JSON contents in a more readable form.