DEV Community

Aaron Saikovski
Aaron Saikovski

Posted on

How to query your GoodWe Solar inverter API using GoLang.

Over the past few months I have been doing more and more GoLang development for my side projects and and I have come to the conclusion that Go strikes a very nice balance of productivity and performance.
The binaries that Go produces eliminates the need for JVMs or runtime frameworks that need to be shipped with your application. Everything you need to run your application is in a single native compiled binary.
As a side note I did have a quick look at Rust as an option too but decided to settle on Go for this project. (I am not a Rust developer and might look at it in the future).

Project Background
Late 2023 I was thinking of a cool project to stretch my Go development skills a bit and came across this article.
I thought that I could come up with good solution written in Go to query my solar inverter API and present some meaningful
reporting data.
People asked me why? Because its a good way to learn how to query APIs and manipulate JSON results in Go. Thats why!
After lots of extensive research and looking at other open source projects I found on Github here, most of which were in Python and were overly complex and required lots of external dependencies. I settled on writing this in Go.

So after some trial and error and a few iterations I managed to come up with a Go based, command line utility to query my Goodwe/SEMs Solar inverter to query the APIs and return a JSON result of the measured inverter metrics. Sweet!
One thing to note was the lack of any solid API documentation from the GooodWe APIs and as such a lot of trial and error was involved to get this to work. If you have any updated API documentation on how their APIs work please let me know. I couldn't find anything. :-(

The solution
To achieve the desirted outcome, required two separate API calls and passing objects (structs in Go):
1) Obtain a login token - Login to the SEMS Portal API - 'https://www.semsportal.com/api/v1/Common/CrossLogin'.
The Go function signature looks something like this:

func Login(LoginCredentials *types.LoginCredentials) (*types.LoginResponse, error) {
Enter fullscreen mode Exit fullscreen mode

2) Then using the login response struct from Step 1 (represented as a JSON Go struct) pass to the inverter API GetMonitorDetailByPowerstationId API here - 'v2/PowerStation/GetMonitorDetailByPowerstationId'.
The Go function signature looks something like this:

func FetchData(Account string, Password string, PowerStationID string, DailySummary bool) error {
Enter fullscreen mode Exit fullscreen mode

3) Then from the 'FetchData()' Go function a JSON object is returned.
The internal Go function uses a 'interfaces.ISemsDataConstraint' interface constraint to restrict the datatypes to the two structs I defined in the types package:

type ISemsDataConstraint interface {
    types.InverterData | types.DailySummaryData
}
Enter fullscreen mode Exit fullscreen mode

The Internal Go function signature to get the monitor data is as follows - note the interface type constraint:

func getMonitorData[T interfaces.ISemsDataConstraint](LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse, InverterOutput *T) error {
Enter fullscreen mode Exit fullscreen mode

There are two output options - Detailed output and a daily summary view. Use the '--dailysummary' flag to produce a daily summary.

The struct for the daily summary data is as follows:

package types

type DailySummaryData struct {
    Language string `json:"language"`
    HasError bool   `json:"hasError"`
    Msg      string `json:"msg"`
    Code     string `json:"code"`
    Data     struct {
        Kpi struct {
            MonthGeneration float64 `json:"month_generation"`
            Power           float64 `json:"power"`
            TotalPower      float64 `json:"total_power"`
            DayIncome       float64 `json:"day_income"`
            TotalIncome     float64 `json:"total_income"`
            Currency        string  `json:"currency"`
        } `json:"kpi"`
        Inverter []struct {
            TotalGeneration string `json:"total_generation"`
            DailyGeneration string `json:"daily_generation"`
        } `json:"inverter"`
    } `json:"data"`
}

Enter fullscreen mode Exit fullscreen mode

The main struct 'inverterdata' is too large to list here but its in the 'types' package here.

The resulting output has had any sensitive data removed from the underlying structs so hopefully no usernames, addresses will be exposed.

The Go project layout has gone through a few refactors as well and I think I have settled on a sensible project and package layout. Hit me up if you agree/disagree.

Some of the big benefits and lessons learned during writing this are
1) Go is awesome at building cross platform/single native binaries with no external runtimes.
2) Produces really clean and easy to read code.
3) Go has really fast performance from compile to runtime.
4) Using some of the advanced Go features such as structs, JSON marshalling, Interfaces and constraints was a learning curve but I think I managed to get my head around these.
5) Pointers in Go are awesome! I used them a lot in this project to pass memory references to structs around and update them by marhalling to JSON.
6) Don't be afraid to refactor your folder and package layouts a few times. I think I did it about five times before I settled on the current layout.
7) Use interfaces and constraints to reduce the amount of code you need to write.

I hope you enjoy exploring this this tool as much as I enjoyed writing it.
Please feel free to provide any feedback and suggestions on how I can make the code better, or if there are any bugs, please report them here.

The code itself can be found here.

Thanks for reading!

Top comments (0)