From b0c1a1e41191397c38e8f25650d4965f24c79873 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Mon, 22 Jan 2024 17:36:57 +1100 Subject: [PATCH 01/26] created v2 branch code --- .DS_Store | Bin 6148 -> 6148 bytes .gitignore | 2 + .goreleaser.yaml | 3 +- CHANGELOG.md | 19 +- LICENSE | 2 +- Makefile | 79 ++++--- README.md | 29 +-- TODO.md | 23 ++- app/app.go | 29 +++ .../authentication.go | 38 ++-- .../authutils.go | 22 +- constants/constants.go | 9 +- go.mod | 2 +- go.sum | 3 +- inverter/data.go | 72 +++++++ .../datautils.go | 20 +- .../inverter.go | 40 ++-- main.go | 51 +---- pkg/goodwe/fetchdata/fetchdata.go | 62 ------ test/README.md | 9 + test/constants_test.go | 51 +++++ test/samplemodule_test.go | 22 ++ test/samplestruct_test.go | 21 ++ ...tationresponsedata.go => inverterrdata.go} | 192 ++++-------------- types/logincredentials.go | 12 ++ .../{semsresponsedata.go => loginresponse.go} | 14 +- types/semslogincreds.go | 8 - utils/args.go | 20 ++ utils/errorhandler.go | 1 + 29 files changed, 473 insertions(+), 382 deletions(-) create mode 100644 app/app.go rename {pkg/goodwe/authentication => authentication}/authentication.go (59%) rename pkg/goodwe/authentication/authhelper.go => authentication/authutils.go (50%) create mode 100644 inverter/data.go rename pkg/goodwe/powerstationdata/powerstationdatahelper.go => inverter/datautils.go (64%) rename pkg/goodwe/powerstationdata/powerstationdata.go => inverter/inverter.go (51%) delete mode 100644 pkg/goodwe/fetchdata/fetchdata.go create mode 100644 test/README.md create mode 100644 test/constants_test.go create mode 100644 test/samplemodule_test.go create mode 100644 test/samplestruct_test.go rename types/{stationresponsedata.go => inverterrdata.go} (72%) create mode 100644 types/logincredentials.go rename types/{semsresponsedata.go => loginresponse.go} (59%) delete mode 100644 types/semslogincreds.go create mode 100644 utils/args.go diff --git a/.DS_Store b/.DS_Store index fc263adf70a6c7437b1d75a1293ca038ae4dc37b..cea1dae78b9ac9912c24376caf148720c4ba0ba5 100644 GIT binary patch delta 43 xcmZoMXfc@J&&V<{z#2&O_z1EvlrW?+6f=~h6es5-<>%*Yd??4hnVsV=KL8Z34TAsx delta 28 icmZoMXfc@J&&WJ6z#2&O_-y -# GoGgoodwe +# GoGoodwe V2.0 -A command line tool and Go packages to query the GOODWE SEMS Portal APIs - written in 100% Go. +A command line tool and query the GOODWE SEMS Inverter APIs - written in 100% Go. [![Build Status](https://github.com/AaronSaikovski/gogoodwe/workflows/build/badge.svg)](https://github.com/AaronSaikovski/gogoodwe/actions) -[![Coverage Status](https://coveralls.io/repos/github/AaronSaikovski/gogoodwe/badge.svg?branch=main)](https://coveralls.io/github/AaronSaikovski/gogoodwe?branch=main) [![Licence](https://img.shields.io/github/license/AaronSaikovski/gogoodwe)](LICENSE) @@ -15,17 +14,19 @@ A command line tool and Go packages to query the GOODWE SEMS Portal APIs - writt The toolchain is mainly driven by the Makefile. ```bash -* help - Display help about make targets for this Makefile -* release - Builds the project in preparation for release -* debug - Builds the project in preparation for debug -* buildandrun - builds and runs the program on the target platform -* run - runs main.go for testing -* clean - Remove the old builds and any debug information -* test - executes unit test -* dep - fetches any external dependencies -* vet - Vet examines Go source code and reports suspicious constructs -* staticcheck - Runs static code analyzer staticcheck -* lint - format code and tidy modules +help - Display help about make targets for this Makefile +release - Builds the project in preparation for (local)release +goreleaser - Builds the project in preparation for release +docs - updates the swagger docs +build - Builds the project in preparation for debug +run - builds and runs the program on the target platform +clean - Remove the old builds and any debug information +test - executes unit tests +deps - fetches any external dependencies and updates +vet - Vet examines Go source code and reports suspicious constructs +staticcheck - Runs static code analyzer staticcheck - currently broken +seccheck - Code vulnerability check +lint - format code and tidy modules ``` To get started type, diff --git a/TODO.md b/TODO.md index 3143314..ef01c7d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,22 @@ -# gogoodwe TODO +# GoGoodwe V2 - TODO -GoGoodwe backlog +### ToDo -### Todo - -- [ ] Add ability to output to file with a flag -- [ ] Add ability to have a smaller output struct of just key reporting data -- [ ] Add Golang contexts for API calls +- [ ] Add ability to output inverter data to a file. +- [ ] Format the inverter output to make it more human readable. +- [ ] Add the ability to query historical data for a single day. +- [ ] Have the ability to have a realtime logging to the screen or to a file in 5 minute intervals. +- [ ] Add the ability to produce a daily summary of key data (Generation today, Income today, total generation, total income). +- [ ] Add the ability to query the inverter status for Generation today and Status (check if operational). +- [ ] Add goroutines and wait groups for the API calls and maybe channels for success/failed API calls. +- [ ] Add Cobra for command flag parsing and processing. +- [ ] Investigate the ability to generate .CSV files as output. +- [ ] ### In Progress +- [ ] add unit tests + ### Done ✓ + +- [ ] diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..2726e33 --- /dev/null +++ b/app/app.go @@ -0,0 +1,29 @@ +package app + +// Main package - This is the main program entry point +import ( + "github.com/AaronSaikovski/gogoodwe/inverter" + "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/alexflint/go-arg" +) + +// Run - main program runner +func Run() error { + + //Get the args input data + var args utils.Args + p := arg.MustParse(&args) + + //check for valid email address input + if !utils.CheckValidEmail(args.Account) { + p.Fail("Invalid Email address format - should be: 'user@somedomain.com'.") + } + + //check for valid powerstation Id + if !utils.CheckValidPowerstationID(args.PowerStationID) { + p.Fail("Invalid Powerstation ID format: - should be: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.") + } + + // Get the data from the API, return any errors. Pass in args as string + return inverter.FetchData(args.Account, args.Password, args.PowerStationID) +} diff --git a/pkg/goodwe/authentication/authentication.go b/authentication/authentication.go similarity index 59% rename from pkg/goodwe/authentication/authentication.go rename to authentication/authentication.go index 5b52eae..85b4bd3 100644 --- a/pkg/goodwe/authentication/authentication.go +++ b/authentication/authentication.go @@ -15,41 +15,34 @@ import ( "github.com/AaronSaikovski/gogoodwe/utils" ) -// SetHeaders - Set the login headers for the SEMS API login -func SetHeaders(r *http.Request) { - r.Header.Add("Content-Type", "application/json") - r.Header.Add("Token", "{\"version\":\"v2.1.0\",\"client\":\"ios\",\"language\":\"en\"}") -} - // DoLogin - Main public login function // Logs into the SEMs API -func DoLogin(SemsResponseData *types.SemsResponseData, UserLogin *types.SemsLoginCreds) error { +func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials) error { //check if the UserLogin struct is empty - usererr := CheckUserLoginInfo(UserLogin) - if usererr != nil { - utils.HandleError(usererr) + if usererr := checkUserLoginInfo(UserLogin); usererr != nil { return usererr } // User login struct to be converted to JSON - jsonData, _ := utils.MarshalStructToJSON(UserLogin) + jsonData, jsonErr := utils.MarshalStructToJSON(UserLogin) + if jsonErr != nil { + return jsonErr + } // Create a new http request - req, err := http.NewRequest(http.MethodPost, constants.AuthLoginUrL, bytes.NewBuffer(jsonData)) + req, err := http.NewRequest(http.MethodPost, constants.AuthLoginUrl, bytes.NewBuffer(jsonData)) if err != nil { - utils.HandleError(err) return err } //Add headers pass in the pointer to set the headers on the request object - SetHeaders(req) + setHeaders(req) //make the API Call client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} resp, err := client.Do(req) if err != nil { - utils.HandleError(err) return err } @@ -57,13 +50,22 @@ func DoLogin(SemsResponseData *types.SemsResponseData, UserLogin *types.SemsLogi defer resp.Body.Close() // Get the response body - respBody, _ := utils.FetchResponseBody(resp.Body) + respBody, respErr := utils.FetchResponseBody(resp.Body) + if respErr != nil { + return respErr + } //marshall response to SemsRespInfo struct - utils.UnmarshalDataToStruct(respBody, &SemsResponseData) + dataErr := utils.UnmarshalDataToStruct(respBody, &SemsResponseData) + if dataErr != nil { + return dataErr + } // check for successful login return value..return a login error - CheckUserLoginResponse(SemsResponseData.Msg) + loginErr := checkUserLoginResponse(SemsResponseData.Msg) + if loginErr != nil { + return loginErr + } return nil diff --git a/pkg/goodwe/authentication/authhelper.go b/authentication/authutils.go similarity index 50% rename from pkg/goodwe/authentication/authhelper.go rename to authentication/authutils.go index b103423..49f004a 100644 --- a/pkg/goodwe/authentication/authhelper.go +++ b/authentication/authutils.go @@ -1,5 +1,5 @@ /* -# Name: authhelper - auth helper functions +# Name: authentication - auth helper functions # Author: Aaron Saikovski - asaikovski@outlook.com */ @@ -7,17 +7,22 @@ package authentication import ( "errors" + "net/http" "strings" "github.com/AaronSaikovski/gogoodwe/constants" "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" ) +// SetHeaders - Set the login headers for the SEMS API login +func setHeaders(r *http.Request) { + r.Header.Add("Content-Type", "application/json") + r.Header.Add("Token", "{\"version\":\"v2.1.0\",\"client\":\"ios\",\"language\":\"en\"}") +} + // CheckUserLoginInfo - Check user login struct is valid/not null -func CheckUserLoginInfo(UserLogin *types.SemsLoginCreds) error { - //check if the UserLogin struct is empty - if (*UserLogin == types.SemsLoginCreds{}) { +func checkUserLoginInfo(UserLogin *types.LoginCredentials) error { + if (*UserLogin == types.LoginCredentials{}) { return errors.New("**Error: User Login details are empty or invalid..**") } else { return nil @@ -25,9 +30,10 @@ func CheckUserLoginInfo(UserLogin *types.SemsLoginCreds) error { } // CheckUserLoginResponse - check for successful login return value..return a login error -func CheckUserLoginResponse(loginResponse string) { +func checkUserLoginResponse(loginResponse string) error { if strings.Compare(loginResponse, constants.SemsLoginSuccessResponse) != 0 { - authErr := errors.New("API Login Error: " + loginResponse) - utils.HandleError(authErr) + return errors.New("**API Login Error: " + loginResponse) + } else { + return nil } } diff --git a/constants/constants.go b/constants/constants.go index cb423b5..70ba5dd 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -1,8 +1,13 @@ +/* +# Name: constants - shared constants +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ + package constants const ( // Auth Login Url - AuthLoginUrL string = "https://www.semsportal.com/api/v2/Common/CrossLogin" + AuthLoginUrl string = "https://www.semsportal.com/api/v2/Common/CrossLogin" // Powerstation API Url PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" @@ -11,7 +16,7 @@ const ( HTTPTimeout = 20 // Version string - VersionString string = "gogoodwe v0.0.7" + VersionString string = "gogoodwe v2.0.0" //API login success response message SemsLoginSuccessResponse string = "Successful" diff --git a/go.mod b/go.mod index c0b1f97..fcddca2 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,4 @@ require ( github.com/logrusorgru/aurora v2.0.3+incompatible ) -require github.com/alexflint/go-scalar v1.1.0 // indirect +require github.com/alexflint/go-scalar v1.2.0 // indirect diff --git a/go.sum b/go.sum index d3619e9..899e74f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA= -github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= +github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= +github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/inverter/data.go b/inverter/data.go new file mode 100644 index 0000000..92d961b --- /dev/null +++ b/inverter/data.go @@ -0,0 +1,72 @@ +/* +# Name: data - fetches data from the goodwe API - and processes it to pass back to caller +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package inverter + +import ( + "errors" + "fmt" + + "github.com/AaronSaikovski/gogoodwe/authentication" + "github.com/AaronSaikovski/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/logrusorgru/aurora" +) + +// apiLogin - Login to the API +func apiLogin(SemsUserLogin *types.LoginCredentials, SemsResponseData *types.LoginResponse) error { + + // Do the login - update the pointer to the struct SemsResponseData + autherr := authentication.DoLogin(SemsResponseData, SemsUserLogin) + if autherr != nil { + utils.HandleError(autherr) + return autherr + } else { + return nil + } +} + +// FetchData - Main API fetch function +func FetchData(Account string, Password string, PowerStationID string) error { + + // Data types + var SemsResponseData types.LoginResponse + var PowerstationData types.InverterData + + // Create a new SemsLoginCreds object via a struct literal + var SemsUserLogin = types.LoginCredentials{ + Account: Account, + Password: Password, + PowerStationID: PowerStationID, + } + + // Do the login..check for errors + err := apiLogin(&SemsUserLogin, &SemsResponseData) + if err == nil { + + // Fetch the data + dataerr := fetchInverterData(&SemsResponseData, &SemsUserLogin, &PowerstationData) + if dataerr != nil { + utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) + return dataerr + } else { + // Get output + dataOutput, jsonerr := GetDataJSON(&PowerstationData) + if jsonerr != nil { + utils.HandleError(errors.New("error: converting powerstation data")) + return jsonerr + + } else { + //Display output + fmt.Println(aurora.BrightYellow(string(dataOutput))) + } + } + + } else { + utils.HandleError(err) + return err + } + + return nil +} diff --git a/pkg/goodwe/powerstationdata/powerstationdatahelper.go b/inverter/datautils.go similarity index 64% rename from pkg/goodwe/powerstationdata/powerstationdatahelper.go rename to inverter/datautils.go index e6a809b..b7c7386 100644 --- a/pkg/goodwe/powerstationdata/powerstationdatahelper.go +++ b/inverter/datautils.go @@ -1,25 +1,31 @@ /* -# Name: powerstationdatahelper - helper functions to get the Powerstation Data from the API +# Name: powerstationhelper - helper functions to get the Powerstation Data from the API # Author: Aaron Saikovski - asaikovski@outlook.com */ -package powerstationdata +package inverter import ( "encoding/json" + "net/http" "strconv" "github.com/AaronSaikovski/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/utils" ) -// DataTokenJSON - Makes a map for the token to be passed to the Data API header and returns a JSON string -func DataTokenJSON(SemsResponseData *types.SemsResponseData) ([]byte, error) { +// setHeaders - Set the headers for the SEMS Data API +func setHeaders(r *http.Request, tokenstring []byte) { + r.Header.Add("Content-Type", "application/json") + r.Header.Add("Token", string(tokenstring)) +} +// DataTokenJSON - Makes a map for the token to be passed to the Data API header and returns a JSON string +func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { tokenMap := make(map[string]string) tokenMap["version"] = "v2.1.0" tokenMap["client"] = "ios" tokenMap["language"] = "en" - tokenMap["timestamp"] = strconv.Itoa(SemsResponseData.Data.Timestamp) + tokenMap["timestamp"] = strconv.FormatInt(SemsResponseData.Data.Timestamp, 10) tokenMap["uid"] = SemsResponseData.Data.UID tokenMap["token"] = SemsResponseData.Data.Token @@ -29,7 +35,7 @@ func DataTokenJSON(SemsResponseData *types.SemsResponseData) ([]byte, error) { } // PowerStationIDJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string -func PowerStationIDJSON(UserLogin *types.SemsLoginCreds) ([]byte, error) { +func powerStationIDJSON(UserLogin *types.LoginCredentials) ([]byte, error) { powerStationMap := make(map[string]string) powerStationMap["powerStationId"] = UserLogin.PowerStationID @@ -39,7 +45,7 @@ func PowerStationIDJSON(UserLogin *types.SemsLoginCreds) ([]byte, error) { } // GetDataJSON - Returns the PowerstationOutputData as JSON -func GetDataJSON(PowerstationOutputData *types.StationResponseData) ([]byte, error) { +func GetDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { // Get the response and return any errors resp, err := utils.MarshalStructToJSON(&PowerstationOutputData) diff --git a/pkg/goodwe/powerstationdata/powerstationdata.go b/inverter/inverter.go similarity index 51% rename from pkg/goodwe/powerstationdata/powerstationdata.go rename to inverter/inverter.go index e4bc859..ae47f7c 100644 --- a/pkg/goodwe/powerstationdata/powerstationdata.go +++ b/inverter/inverter.go @@ -1,8 +1,8 @@ /* -# Name: powerstationdata - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" +# Name: inverter - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" # Author: Aaron Saikovski - asaikovski@outlook.com */ -package powerstationdata +package inverter import ( "bytes" @@ -14,22 +14,20 @@ import ( "github.com/AaronSaikovski/gogoodwe/utils" ) -// setHeaders - Set the headers for the SEMS Data API -func setHeaders(r *http.Request, tokenstring []byte) { - r.Header.Add("Content-Type", "application/json") - r.Header.Add("Token", string(tokenstring)) -} - -// FetchData - Fetches Data from the specified PowerstationID via tht SEMs API -func FetchData(SemsResponseData *types.SemsResponseData, - UserLogin *types.SemsLoginCreds, - PowerstationOutputData *types.StationResponseData) error { +// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using theSEMs API +func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials, PowerstationOutputData *types.InverterData) error { // get the Token header data - tokenMapJSONData, _ := DataTokenJSON(SemsResponseData) + tokenMapJSONData, tokenMapJSONErr := dataTokenJSON(SemsResponseData) + if tokenMapJSONErr != nil { + return tokenMapJSONErr + } // get the Powerstation ID header data - powerStationMapJSONData, _ := PowerStationIDJSON(UserLogin) + powerStationMapJSONData, powerStationMapJSONErr := powerStationIDJSON(UserLogin) + if powerStationMapJSONErr != nil { + return powerStationMapJSONErr + } //Get the url from the Auth API and append the data url part url := SemsResponseData.API + constants.PowerStationURL @@ -37,7 +35,7 @@ func FetchData(SemsResponseData *types.SemsResponseData, // Create a new http request req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) if err != nil { - utils.HandleError(err) + return err } //Add headers pass in the pointer to set the headers on the request object @@ -47,7 +45,6 @@ func FetchData(SemsResponseData *types.SemsResponseData, client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} resp, err := client.Do(req) if err != nil { - utils.HandleError(err) return err } @@ -55,12 +52,15 @@ func FetchData(SemsResponseData *types.SemsResponseData, defer resp.Body.Close() // Get the response body - respBody, _ := utils.FetchResponseBody(resp.Body) + respBody, respBodyErr := utils.FetchResponseBody(resp.Body) + if respBodyErr != nil { + return respBodyErr + } //marshall response to SemsRespInfo struct - dataerr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) - if dataerr != nil { - return dataerr + dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) + if dataStructErr != nil { + return dataStructErr } return nil diff --git a/main.go b/main.go index 62ad0ad..a9fe9bb 100644 --- a/main.go +++ b/main.go @@ -1,59 +1,20 @@ /* -# Name: main package - Authenticates to and queries the SEMS Solar inverter API +# Name: GoGoodwe - Authenticates to and queries the SEMS Solar inverter API # Author: Aaron Saikovski - asaikovski@outlook.com */ package main -// Main package - This is the main program entry point import ( - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/pkg/goodwe/fetchdata" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" - "github.com/alexflint/go-arg" + "github.com/AaronSaikovski/gogoodwe/app" ) -// args - srtruct using go-arg- https://github.com/alexflint/go-arg -type args struct { - Account string `arg:"required,-a,--account" help:"SEMS Email Account."` - Pwd string `arg:"required,-p,--pwd" help:"SEMS Account password."` - PowerStationID string `arg:"required,-i,--powerstationid" help:"SEMS Powerstation ID."` -} - -// Description - App description -func (args) Description() string { - return "A command line tool and GoLang package to query the GOODWE SEMS Portal APIs and Solar SEMS API." -} - -// Version - Version info -func (args) Version() string { - return constants.VersionString -} - // main - program main func main() { - //Get the args input data - var args args - p := arg.MustParse(&args) + //setup and run app + err := app.Run() - //check for valid email address input - if !utils.CheckValidEmail(args.Account) { - p.Fail("Invalid Email address format - should be: 'user@somedomain.com'.") + if err != nil { + panic(err) } - - //check for valid powerstation Id - if !utils.CheckValidPowerstationID(args.PowerStationID) { - p.Fail("Invalid Powerstation ID format: - should be: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'.") - } - - // Create a new SemsLoginCreds object via a struct literal - SemsUserLogin := types.SemsLoginCreds{ - Account: args.Account, - Pwd: args.Pwd, - PowerStationID: args.PowerStationID, - } - - // Get the data from the API - fetchdata.GetData(&SemsUserLogin) } diff --git a/pkg/goodwe/fetchdata/fetchdata.go b/pkg/goodwe/fetchdata/fetchdata.go deleted file mode 100644 index e12d91b..0000000 --- a/pkg/goodwe/fetchdata/fetchdata.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -# Name: fetchdata - fetches data from the goodwe API - and processes it to pass back to caller -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package fetchdata - -import ( - "errors" - "fmt" - - "github.com/AaronSaikovski/gogoodwe/pkg/goodwe/authentication" - "github.com/AaronSaikovski/gogoodwe/pkg/goodwe/powerstationdata" - - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" - "github.com/logrusorgru/aurora" -) - -// doLogin - Login to the API -func doLogin(SemsUserLogin *types.SemsLoginCreds, SemsResponseData *types.SemsResponseData) error { - - // Do the login - update the pointer to the struct SemsResponseData - autherr := authentication.DoLogin(SemsResponseData, SemsUserLogin) - if autherr != nil { - utils.HandleError(autherr) - return autherr - } else { - return nil - } -} - -// GetData - Main process data function -func GetData(SemsUserLogin *types.SemsLoginCreds) { - - // Data types - var SemsResponseData types.SemsResponseData - var PowerstationData types.StationResponseData - - // Do the login..check for errors - err := doLogin(SemsUserLogin, &SemsResponseData) - if err == nil { - - // Fetch the data - dataerr := powerstationdata.FetchData(&SemsResponseData, SemsUserLogin, &PowerstationData) - if dataerr != nil { - utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) - } else { - // Get output - dataOutput, jsonerr := powerstationdata.GetDataJSON(&PowerstationData) - if jsonerr != nil { - utils.HandleError(errors.New("error: converting powerstation data")) - - } else { - //Display output - fmt.Println(aurora.BrightYellow(string(dataOutput))) - } - } - - } else { - utils.HandleError(err) - } -} diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..cdcf65f --- /dev/null +++ b/test/README.md @@ -0,0 +1,9 @@ +# `/test` + +Additional external test apps and test data. Feel free to structure the `/test` directory anyway you want. For bigger projects it makes sense to have a data subdirectory. For example, you can have `/test/data` or `/test/testdata` if you need Go to ignore what's in that directory. Note that Go will also ignore directories or files that begin with "." or "_", so you have more flexibility in terms of how you name your test data directory. + +Examples: + +* https://github.com/openshift/origin/tree/master/test (test data is in the `/testdata` subdirectory) + + diff --git a/test/constants_test.go b/test/constants_test.go new file mode 100644 index 0000000..5b95341 --- /dev/null +++ b/test/constants_test.go @@ -0,0 +1,51 @@ +package testing + +import ( + "testing" + + "github.com/AaronSaikovski/gogoodwe/constants" +) + +// TestAuthLoginUrl Test Auth Login Url +func TestAuthLoginUrl(t *testing.T) { + + AuthLoginUrlExpected := "https://www.semsportal.com/api/v2/Common/CrossLogin" + if constants.AuthLoginUrl != AuthLoginUrlExpected { + t.Errorf("AuthLoginUrl Const expected '%s' but got '%s'", AuthLoginUrlExpected, constants.AuthLoginUrl) + } +} + +// TestPowerStationURL Test Powerstation API Url +func TestPowerStationURL(t *testing.T) { + + PowerStationURLExpected := "v2/PowerStation/GetMonitorDetailByPowerstationId" + if constants.PowerStationURL != PowerStationURLExpected { + t.Errorf("PowerStationURL const expected '%s' but got '%s'", PowerStationURLExpected, constants.PowerStationURL) + } +} + +// HTTPTimeout Test HTTPTimeout value +// func TestHTTPTimeout(t *testing.T) { + +// HTTPTimeoutPowerStationURLExpected = 20 + +// PowerStationURLExpected := "v2/PowerStation/GetMonitorDetailByPowerstationId" +// if constants.HTTPTimeout != PowerStationURLExpected { +// t.Errorf("PowerStationURL const expected '%d' but got '%d'", PowerStationURLExpected, constants.PowerStationURL) +// } +// } + +// Default timeout value + +//API login success response message +//SemsLoginSuccessResponse string = "Successful" + +// TestSemsLoginSuccessResponse Test SemsLoginSuccessResponse test +func TestSemsLoginSuccessResponse(t *testing.T) { + + SemsLoginSuccessResponseExpected := "Successful" + + if constants.SemsLoginSuccessResponse != SemsLoginSuccessResponseExpected { + t.Errorf("SemsLoginSuccessResponse const expected '%s' but got '%s'", SemsLoginSuccessResponseExpected, constants.SemsLoginSuccessResponse) + } +} diff --git a/test/samplemodule_test.go b/test/samplemodule_test.go new file mode 100644 index 0000000..c7bdb64 --- /dev/null +++ b/test/samplemodule_test.go @@ -0,0 +1,22 @@ +package testing + +/* +A Sample test harness. +*/ + +// import ( +// "testing" + +// "github.com/AaronSaikovski/gostarter/internal/pkg/samplemodule" +// ) + +// // A testing function. +// func TestSampleFunction(t *testing.T) { + +// msg := samplemodule.SampleFunction() +// expected := "OK" + +// if msg != expected { +// t.Errorf("Module expected '%q' but got '%q'", expected, msg) +// } +// } diff --git a/test/samplestruct_test.go b/test/samplestruct_test.go new file mode 100644 index 0000000..d34e611 --- /dev/null +++ b/test/samplestruct_test.go @@ -0,0 +1,21 @@ +/* +A Sample test harness. +*/ + +package testing + +// import ( +// "github.com/AaronSaikovski/gostarter/internal/app/types" +// "testing" +// ) + +// // A testing function. +// func TestSampleStructString(t *testing.T) { + +// expected := "test data" +// ateststruct := types.Sample{SampleString: "test data", SampleInt: 1} + +// if ateststruct.SampleString != expected { +// t.Errorf("struct expected '%s' but got '%s'", expected, ateststruct.SampleString) +// } +// } diff --git a/types/stationresponsedata.go b/types/inverterrdata.go similarity index 72% rename from types/stationresponsedata.go rename to types/inverterrdata.go index 8239905..81cdbfb 100644 --- a/types/stationresponsedata.go +++ b/types/inverterrdata.go @@ -1,43 +1,18 @@ +/* +# Name: InverterData - Struct to hold data returned from the Powerstation API +# Minimised version - removed any sensitive data +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ package types -// StationResponseData - Struct to hold data returned from the Powerstation API -type StationResponseData struct { +// InverterData - Struct to hold data returned from the Inverter Powerstation API +type InverterData struct { Language string `json:"language"` Function []string `json:"function"` HasError bool `json:"hasError"` Msg string `json:"msg"` Code string `json:"code"` Data struct { - Info struct { - PowerstationID string `json:"powerstation_id"` - Time string `json:"time"` - DateFormat string `json:"date_format"` - DateFormatYm string `json:"date_format_ym"` - Stationname string `json:"stationname"` - Address string `json:"address"` - OwnerName string `json:"owner_name"` - OwnerPhone string `json:"owner_phone"` - OwnerEmail string `json:"owner_email"` - BatteryCapacity float64 `json:"battery_capacity"` - TurnonTime string `json:"turnon_time"` - CreateTime string `json:"create_time"` - Capacity float64 `json:"capacity"` - Longitude float64 `json:"longitude"` - Latitude float64 `json:"latitude"` - PowerstationType string `json:"powerstation_type"` - Status int `json:"status"` - IsStored bool `json:"is_stored"` - IsPowerflow bool `json:"is_powerflow"` - ChartsType int `json:"charts_type"` - HasPv bool `json:"has_pv"` - HasStatisticsCharts bool `json:"has_statistics_charts"` - OnlyBps bool `json:"only_bps"` - OnlyBpu bool `json:"only_bpu"` - TimeSpan float64 `json:"time_span"` - PrValue string `json:"pr_value"` - OrgCode string `json:"org_code"` - OrgName string `json:"org_name"` - } `json:"info"` Kpi struct { MonthGeneration float64 `json:"month_generation"` Pac float64 `json:"pac"` @@ -50,80 +25,18 @@ type StationResponseData struct { } `json:"kpi"` PowercontrolStatus int `json:"powercontrol_status"` Images []any `json:"images"` - Weather struct { - HeWeather6 []struct { - Basic struct { - Cid any `json:"cid"` - Location any `json:"location"` - ParentCity any `json:"parent_city"` - AdminArea any `json:"admin_area"` - Cnty any `json:"cnty"` - Lat any `json:"lat"` - Lon any `json:"lon"` - Tz any `json:"tz"` - } `json:"basic"` - Update struct { - Loc any `json:"loc"` - Utc any `json:"utc"` - } `json:"update"` - Status string `json:"status"` - DailyForecast []struct { - CondCodeD string `json:"cond_code_d"` - CondCodeN string `json:"cond_code_n"` - CondTxtD string `json:"cond_txt_d"` - CondTxtN string `json:"cond_txt_n"` - Date string `json:"date"` - Hum string `json:"hum"` - Pcpn string `json:"pcpn"` - Pop string `json:"pop"` - Pres string `json:"pres"` - TmpMax string `json:"tmp_max"` - TmpMin string `json:"tmp_min"` - UvIndex string `json:"uv_index"` - Vis string `json:"vis"` - WindDeg string `json:"wind_deg"` - WindDir string `json:"wind_dir"` - WindSc string `json:"wind_sc"` - WindSpd string `json:"wind_spd"` - } `json:"daily_forecast"` - } `json:"HeWeather6"` - } `json:"weather"` - Inverter []struct { - Sn string `json:"sn"` - Dict struct { - Left []struct { - IsHT bool `json:"isHT"` - IsStoreSkip bool `json:"isStoreSkip"` - Key string `json:"key"` - Value string `json:"value"` - Unit string `json:"unit"` - IsFaultMsg int `json:"isFaultMsg"` - FaultMsgCode int `json:"faultMsgCode"` - } `json:"left"` - Right []struct { - IsHT bool `json:"isHT"` - IsStoreSkip bool `json:"isStoreSkip"` - Key string `json:"key"` - Value string `json:"value"` - Unit string `json:"unit"` - IsFaultMsg int `json:"isFaultMsg"` - FaultMsgCode int `json:"faultMsgCode"` - } `json:"right"` - } `json:"dict"` - IsStored bool `json:"is_stored"` - Name string `json:"name"` - InPac float64 `json:"in_pac"` - OutPac float64 `json:"out_pac"` - Eday float64 `json:"eday"` - Emonth float64 `json:"emonth"` - Etotal float64 `json:"etotal"` - Status int `json:"status"` - TurnonTime string `json:"turnon_time"` - ReleationID string `json:"releation_id"` - Type string `json:"type"` - Capacity float64 `json:"capacity"` - D struct { - PwID string `json:"pw_id"` + Inverter []struct { + IsStored bool `json:"is_stored"` + InPac float64 `json:"in_pac"` + OutPac float64 `json:"out_pac"` + Eday float64 `json:"eday"` + Emonth float64 `json:"emonth"` + Etotal float64 `json:"etotal"` + Status int `json:"status"` + TurnonTime string `json:"turnon_time"` + Type string `json:"type"` + Capacity float64 `json:"capacity"` + D struct { Capacity string `json:"capacity"` Model string `json:"model"` OutputPower string `json:"output_power"` @@ -197,10 +110,7 @@ type StationResponseData struct { InvertFull struct { CtSolutionType int `json:"ct_solution_type"` Cts any `json:"cts"` - Sn string `json:"sn"` CheckCode string `json:"check_code"` - PowerstationID string `json:"powerstation_id"` - Name string `json:"name"` ModelType string `json:"model_type"` ChangeType int `json:"change_type"` ChangeTime int `json:"change_time"` @@ -361,43 +271,29 @@ type StationResponseData struct { BatteryCharging string `json:"battery_charging"` LastRefreshTime string `json:"last_refresh_time"` BmsStatus string `json:"bms_status"` - PwID string `json:"pw_id"` FaultMessage string `json:"fault_message"` WarningCode any `json:"warning_code"` BatteryPower float64 `json:"battery_power"` - PointIndex string `json:"point_index"` - Points []struct { - TargetIndex int `json:"target_index"` - TargetName string `json:"target_name"` - Display string `json:"display"` - Unit string `json:"unit"` - TargetKey string `json:"target_key"` - TextCn string `json:"text_cn"` - TargetSnSix any `json:"target_sn_six"` - TargetSnSeven any `json:"target_sn_seven"` - TargetType any `json:"target_type"` - StorageName any `json:"storage_name"` - } `json:"points"` - BackupPloadS float64 `json:"backup_pload_s"` - BackupVloadS float64 `json:"backup_vload_s"` - BackupIloadS float64 `json:"backup_iload_s"` - BackupPloadT float64 `json:"backup_pload_t"` - BackupVloadT float64 `json:"backup_vload_t"` - BackupIloadT float64 `json:"backup_iload_t"` - EtotalBuy any `json:"etotal_buy"` - EdayBuy float64 `json:"eday_buy"` - EbatteryCharge any `json:"ebattery_charge"` - EchargeDay float64 `json:"echarge_day"` - EbatteryDischarge any `json:"ebattery_discharge"` - EdischargeDay float64 `json:"edischarge_day"` - BattStrings any `json:"batt_strings"` - MeterConnectStatus any `json:"meter_connect_status"` - MtactivepowerR float64 `json:"mtactivepower_r"` - MtactivepowerS float64 `json:"mtactivepower_s"` - MtactivepowerT float64 `json:"mtactivepower_t"` - HasTigo bool `json:"has_tigo"` - CanStartIV bool `json:"canStartIV"` - BatteryCount any `json:"battery_count"` + BackupPloadS float64 `json:"backup_pload_s"` + BackupVloadS float64 `json:"backup_vload_s"` + BackupIloadS float64 `json:"backup_iload_s"` + BackupPloadT float64 `json:"backup_pload_t"` + BackupVloadT float64 `json:"backup_vload_t"` + BackupIloadT float64 `json:"backup_iload_t"` + EtotalBuy any `json:"etotal_buy"` + EdayBuy float64 `json:"eday_buy"` + EbatteryCharge any `json:"ebattery_charge"` + EchargeDay float64 `json:"echarge_day"` + EbatteryDischarge any `json:"ebattery_discharge"` + EdischargeDay float64 `json:"edischarge_day"` + BattStrings any `json:"batt_strings"` + MeterConnectStatus any `json:"meter_connect_status"` + MtactivepowerR float64 `json:"mtactivepower_r"` + MtactivepowerS float64 `json:"mtactivepower_s"` + MtactivepowerT float64 `json:"mtactivepower_t"` + HasTigo bool `json:"has_tigo"` + CanStartIV bool `json:"canStartIV"` + BatteryCount any `json:"battery_count"` } `json:"inverter"` Hjgx struct { Co2 float64 `json:"co2"` @@ -466,7 +362,6 @@ type StationResponseData struct { Environmental []any `json:"environmental"` Equipment []struct { Type string `json:"type"` - Title string `json:"title"` Status int `json:"status"` Model any `json:"model"` StatusText any `json:"statusText"` @@ -479,8 +374,6 @@ type StationResponseData struct { IsStored bool `json:"isStored"` Soc string `json:"soc"` IsChange bool `json:"isChange"` - RelationID string `json:"relationId"` - Sn string `json:"sn"` HasTigo bool `json:"has_tigo"` IsSec bool `json:"is_sec"` IsSecs bool `json:"is_secs"` @@ -489,11 +382,4 @@ type StationResponseData struct { TitleSn any `json:"titleSn"` } `json:"equipment"` } `json:"data"` - Components struct { - Para string `json:"para"` - LangVer int `json:"langVer"` - TimeSpan int `json:"timeSpan"` - API string `json:"api"` - MsgSocketAdr any `json:"msgSocketAdr"` - } `json:"components"` } diff --git a/types/logincredentials.go b/types/logincredentials.go new file mode 100644 index 0000000..64a12c1 --- /dev/null +++ b/types/logincredentials.go @@ -0,0 +1,12 @@ +/* +# Name: LoginCredentials - Struct to hold User login data +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package types + +// LoginCredentials - Struct to hold User login data +type LoginCredentials struct { + Account string `json:"account"` + Password string `json:"pwd"` + PowerStationID string `json:"powerstationid"` +} diff --git a/types/semsresponsedata.go b/types/loginresponse.go similarity index 59% rename from types/semsresponsedata.go rename to types/loginresponse.go index 5abcd61..5d11885 100644 --- a/types/semsresponsedata.go +++ b/types/loginresponse.go @@ -1,15 +1,19 @@ +/* +# Name: LoginResponse - SEMS API Response Data struct +# Contains all the JSON Response data returned from the authentication API - "https://www.semsportal.com/api/v2/Common/CrossLogin" +# Will be unmarshalled to a struct via a pointer +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ package types -// SemsResponseData - SEMS API Response Data struct -// Contains all the JSON Response data returned from the authentication API - "https://www.semsportal.com/api/v2/Common/CrossLogin" -// Will be unmarshalled to a struct via a pointer -type SemsResponseData struct { +// LoginResponse - SEMS API Response Data struct +type LoginResponse struct { HasError bool `json:"hasError"` Code int32 `json:"code"` Msg string `json:"msg"` Data struct { UID string `json:"uid"` - Timestamp int `json:"timestamp"` + Timestamp int64 `json:"timestamp"` Token string `json:"token"` Client string `json:"client"` Version string `json:"version"` diff --git a/types/semslogincreds.go b/types/semslogincreds.go deleted file mode 100644 index b08db27..0000000 --- a/types/semslogincreds.go +++ /dev/null @@ -1,8 +0,0 @@ -package types - -// SemsLoginCreds - Struct to hold User login data -type SemsLoginCreds struct { - Account string `json:"account"` - Pwd string `json:"pwd"` - PowerStationID string `json:"powerstationid"` -} diff --git a/utils/args.go b/utils/args.go new file mode 100644 index 0000000..56786cd --- /dev/null +++ b/utils/args.go @@ -0,0 +1,20 @@ +package utils + +import "github.com/AaronSaikovski/gogoodwe/constants" + +// Args - struct using go-arg- https://github.com/alexflint/go-arg +type Args struct { + Account string `arg:"required,-a,--account" help:"SEMS Email Account."` + Password string `arg:"required,-p,--password" help:"SEMS Account password."` + PowerStationID string `arg:"required,-i,--powerstationid" help:"SEMS Powerstation ID."` +} + +// Description - App description +func (Args) Description() string { + return "A command line tool to query the GOODWE SEMS Portal APIs and Solar SEMS API." +} + +// Version - Version info +func (Args) Version() string { + return constants.VersionString +} diff --git a/utils/errorhandler.go b/utils/errorhandler.go index a365476..2beccae 100644 --- a/utils/errorhandler.go +++ b/utils/errorhandler.go @@ -2,6 +2,7 @@ package utils import ( "log" + "github.com/logrusorgru/aurora" ) From 1071d96af53fc1e4fd703637da15bfb025ffa720 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Mon, 22 Jan 2024 17:51:35 +1100 Subject: [PATCH 02/26] updated .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 15f4493..e81ff97 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +.DS_Store # Test binary, built with `go test -c` *.test From 24faa1e060d098ba07e2a6f91975cea592c97239 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Mon, 22 Jan 2024 17:52:10 +1100 Subject: [PATCH 03/26] updated .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e81ff97..e9d44f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ *.dll *.so *.dylib -.DS_Store +*.DS_Store # Test binary, built with `go test -c` *.test From e07b5d508eee6b903eb0e0c62c480f46acf48d6e Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 10:16:07 +1100 Subject: [PATCH 04/26] big refactor --- .DS_Store | Bin 6148 -> 6148 bytes Makefile | 2 +- {app => cmd/gogoodwe/app}/app.go | 4 +-- .../authentication}/authentication.go | 6 ++-- .../gogoodwe/authentication}/authutils.go | 4 +-- .../gogoodwe/constants}/constants.go | 0 .../gogoodwe/inverter/fetchdata.go | 23 ++++------------ .../gogoodwe/inverter}/inverter.go | 26 +++++++++--------- cmd/gogoodwe/login/apilogin.go | 24 ++++++++++++++++ main.go => cmd/gogoodwe/main.go | 6 +++- .../gogoodwe/types}/inverterrdata.go | 0 .../gogoodwe/types}/logincredentials.go | 0 .../gogoodwe/types}/loginresponse.go | 0 {utils => cmd/gogoodwe/utils}/args.go | 2 +- {utils => cmd/gogoodwe/utils}/errorhandler.go | 0 cmd/gogoodwe/utils/headerutils.go | 15 ++++++++++ .../gogoodwe/utils/jsonutils.go | 18 ++++-------- {utils => cmd/gogoodwe/utils}/paramcheck.go | 0 .../gogoodwe/utils}/responsehandler.go | 0 19 files changed, 76 insertions(+), 54 deletions(-) rename {app => cmd/gogoodwe/app}/app.go (86%) rename {authentication => cmd/gogoodwe/authentication}/authentication.go (90%) rename {authentication => cmd/gogoodwe/authentication}/authutils.go (89%) rename {constants => cmd/gogoodwe/constants}/constants.go (100%) rename inverter/data.go => cmd/gogoodwe/inverter/fetchdata.go (67%) rename {inverter => cmd/gogoodwe/inverter}/inverter.go (70%) create mode 100644 cmd/gogoodwe/login/apilogin.go rename main.go => cmd/gogoodwe/main.go (75%) rename {types => cmd/gogoodwe/types}/inverterrdata.go (100%) rename {types => cmd/gogoodwe/types}/logincredentials.go (100%) rename {types => cmd/gogoodwe/types}/loginresponse.go (100%) rename {utils => cmd/gogoodwe/utils}/args.go (90%) rename {utils => cmd/gogoodwe/utils}/errorhandler.go (100%) create mode 100644 cmd/gogoodwe/utils/headerutils.go rename inverter/datautils.go => cmd/gogoodwe/utils/jsonutils.go (70%) rename {utils => cmd/gogoodwe/utils}/paramcheck.go (100%) rename {utils => cmd/gogoodwe/utils}/responsehandler.go (100%) diff --git a/.DS_Store b/.DS_Store index cea1dae78b9ac9912c24376caf148720c4ba0ba5..a883a69871e0d208d3afe2ce3fc6a3b931fc13b1 100644 GIT binary patch delta 33 ncmZoMXfc@J&&WD4z#2&O_^>lGBs1hPq-;E+!M>TD<1aq|pMwdJ delta 29 jcmZoMXfc@J&&V<{z#2&O_-uSF!#-JnQFt>u$6tN`il+%1 diff --git a/Makefile b/Makefile index 6d7edde..70ed7b3 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ GO = go GOFLAGS = -ldflags="-s -w" TARGET = gogoodwe -MAINAPPPATH = ./main.go +MAINAPPPATH = ./cmd/${TARGET}/main.go default: help diff --git a/app/app.go b/cmd/gogoodwe/app/app.go similarity index 86% rename from app/app.go rename to cmd/gogoodwe/app/app.go index 2726e33..5c5cca8 100644 --- a/app/app.go +++ b/cmd/gogoodwe/app/app.go @@ -2,8 +2,8 @@ package app // Main package - This is the main program entry point import ( - "github.com/AaronSaikovski/gogoodwe/inverter" - "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/inverter" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" "github.com/alexflint/go-arg" ) diff --git a/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go similarity index 90% rename from authentication/authentication.go rename to cmd/gogoodwe/authentication/authentication.go index 85b4bd3..c498946 100644 --- a/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -10,9 +10,9 @@ import ( "net/http" "time" - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) // DoLogin - Main public login function diff --git a/authentication/authutils.go b/cmd/gogoodwe/authentication/authutils.go similarity index 89% rename from authentication/authutils.go rename to cmd/gogoodwe/authentication/authutils.go index 49f004a..e3ab4d2 100644 --- a/authentication/authutils.go +++ b/cmd/gogoodwe/authentication/authutils.go @@ -10,8 +10,8 @@ import ( "net/http" "strings" - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" ) // SetHeaders - Set the login headers for the SEMS API login diff --git a/constants/constants.go b/cmd/gogoodwe/constants/constants.go similarity index 100% rename from constants/constants.go rename to cmd/gogoodwe/constants/constants.go diff --git a/inverter/data.go b/cmd/gogoodwe/inverter/fetchdata.go similarity index 67% rename from inverter/data.go rename to cmd/gogoodwe/inverter/fetchdata.go index 92d961b..4471656 100644 --- a/inverter/data.go +++ b/cmd/gogoodwe/inverter/fetchdata.go @@ -8,25 +8,12 @@ import ( "errors" "fmt" - "github.com/AaronSaikovski/gogoodwe/authentication" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/login" "github.com/logrusorgru/aurora" ) -// apiLogin - Login to the API -func apiLogin(SemsUserLogin *types.LoginCredentials, SemsResponseData *types.LoginResponse) error { - - // Do the login - update the pointer to the struct SemsResponseData - autherr := authentication.DoLogin(SemsResponseData, SemsUserLogin) - if autherr != nil { - utils.HandleError(autherr) - return autherr - } else { - return nil - } -} - // FetchData - Main API fetch function func FetchData(Account string, Password string, PowerStationID string) error { @@ -42,7 +29,7 @@ func FetchData(Account string, Password string, PowerStationID string) error { } // Do the login..check for errors - err := apiLogin(&SemsUserLogin, &SemsResponseData) + err := login.ApiLogin(&SemsUserLogin, &SemsResponseData) if err == nil { // Fetch the data @@ -52,7 +39,7 @@ func FetchData(Account string, Password string, PowerStationID string) error { return dataerr } else { // Get output - dataOutput, jsonerr := GetDataJSON(&PowerstationData) + dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) if jsonerr != nil { utils.HandleError(errors.New("error: converting powerstation data")) return jsonerr diff --git a/inverter/inverter.go b/cmd/gogoodwe/inverter/inverter.go similarity index 70% rename from inverter/inverter.go rename to cmd/gogoodwe/inverter/inverter.go index ae47f7c..bb7fa72 100644 --- a/inverter/inverter.go +++ b/cmd/gogoodwe/inverter/inverter.go @@ -9,24 +9,24 @@ import ( "net/http" "time" - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using theSEMs API func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials, PowerstationOutputData *types.InverterData) error { // get the Token header data - tokenMapJSONData, tokenMapJSONErr := dataTokenJSON(SemsResponseData) - if tokenMapJSONErr != nil { - return tokenMapJSONErr + tokenMapJSONData, err := utils.DataTokenJSON(SemsResponseData) + if err != nil { + return err } // get the Powerstation ID header data - powerStationMapJSONData, powerStationMapJSONErr := powerStationIDJSON(UserLogin) - if powerStationMapJSONErr != nil { - return powerStationMapJSONErr + powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLogin) + if err != nil { + return err } //Get the url from the Auth API and append the data url part @@ -39,7 +39,7 @@ func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.L } //Add headers pass in the pointer to set the headers on the request object - setHeaders(req, tokenMapJSONData) + utils.SetHeaders(req, tokenMapJSONData) //make the API Call client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} @@ -52,9 +52,9 @@ func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.L defer resp.Body.Close() // Get the response body - respBody, respBodyErr := utils.FetchResponseBody(resp.Body) - if respBodyErr != nil { - return respBodyErr + respBody, err := utils.FetchResponseBody(resp.Body) + if err != nil { + return err } //marshall response to SemsRespInfo struct diff --git a/cmd/gogoodwe/login/apilogin.go b/cmd/gogoodwe/login/apilogin.go new file mode 100644 index 0000000..4ffba73 --- /dev/null +++ b/cmd/gogoodwe/login/apilogin.go @@ -0,0 +1,24 @@ +/* +# Name: apiLogin - Logs in to the SEMS API +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package login + +import ( + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/authentication" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +// apiLogin - Login to the API +func ApiLogin(UserLoginCreds *types.LoginCredentials, UserLoginResponse *types.LoginResponse) error { + + // Do the login - update the pointer to the struct SemsResponseData + err := authentication.DoLogin(UserLoginResponse, UserLoginCreds) + if err != nil { + utils.HandleError(err) + return err + } else { + return nil + } +} diff --git a/main.go b/cmd/gogoodwe/main.go similarity index 75% rename from main.go rename to cmd/gogoodwe/main.go index a9fe9bb..ebee618 100644 --- a/main.go +++ b/cmd/gogoodwe/main.go @@ -5,7 +5,9 @@ package main import ( - "github.com/AaronSaikovski/gogoodwe/app" + "os" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/app" ) // main - program main @@ -16,5 +18,7 @@ func main() { if err != nil { panic(err) + os.Exit(1) } + os.Exit(0) } diff --git a/types/inverterrdata.go b/cmd/gogoodwe/types/inverterrdata.go similarity index 100% rename from types/inverterrdata.go rename to cmd/gogoodwe/types/inverterrdata.go diff --git a/types/logincredentials.go b/cmd/gogoodwe/types/logincredentials.go similarity index 100% rename from types/logincredentials.go rename to cmd/gogoodwe/types/logincredentials.go diff --git a/types/loginresponse.go b/cmd/gogoodwe/types/loginresponse.go similarity index 100% rename from types/loginresponse.go rename to cmd/gogoodwe/types/loginresponse.go diff --git a/utils/args.go b/cmd/gogoodwe/utils/args.go similarity index 90% rename from utils/args.go rename to cmd/gogoodwe/utils/args.go index 56786cd..8260ef2 100644 --- a/utils/args.go +++ b/cmd/gogoodwe/utils/args.go @@ -1,6 +1,6 @@ package utils -import "github.com/AaronSaikovski/gogoodwe/constants" +import "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" // Args - struct using go-arg- https://github.com/alexflint/go-arg type Args struct { diff --git a/utils/errorhandler.go b/cmd/gogoodwe/utils/errorhandler.go similarity index 100% rename from utils/errorhandler.go rename to cmd/gogoodwe/utils/errorhandler.go diff --git a/cmd/gogoodwe/utils/headerutils.go b/cmd/gogoodwe/utils/headerutils.go new file mode 100644 index 0000000..e96fa9b --- /dev/null +++ b/cmd/gogoodwe/utils/headerutils.go @@ -0,0 +1,15 @@ +/* +# Name: powerstationhelper - helper functions to get the Powerstation Data from the API +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package utils + +import ( + "net/http" +) + +// setHeaders - Set the headers for the SEMS Data API +func SetHeaders(r *http.Request, tokenstring []byte) { + r.Header.Add("Content-Type", "application/json") + r.Header.Add("Token", string(tokenstring)) +} diff --git a/inverter/datautils.go b/cmd/gogoodwe/utils/jsonutils.go similarity index 70% rename from inverter/datautils.go rename to cmd/gogoodwe/utils/jsonutils.go index b7c7386..71fc457 100644 --- a/inverter/datautils.go +++ b/cmd/gogoodwe/utils/jsonutils.go @@ -2,25 +2,17 @@ # Name: powerstationhelper - helper functions to get the Powerstation Data from the API # Author: Aaron Saikovski - asaikovski@outlook.com */ -package inverter +package utils import ( "encoding/json" - "net/http" "strconv" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" ) -// setHeaders - Set the headers for the SEMS Data API -func setHeaders(r *http.Request, tokenstring []byte) { - r.Header.Add("Content-Type", "application/json") - r.Header.Add("Token", string(tokenstring)) -} - // DataTokenJSON - Makes a map for the token to be passed to the Data API header and returns a JSON string -func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { +func DataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { tokenMap := make(map[string]string) tokenMap["version"] = "v2.1.0" tokenMap["client"] = "ios" @@ -35,7 +27,7 @@ func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { } // PowerStationIDJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string -func powerStationIDJSON(UserLogin *types.LoginCredentials) ([]byte, error) { +func PowerStationIDJSON(UserLogin *types.LoginCredentials) ([]byte, error) { powerStationMap := make(map[string]string) powerStationMap["powerStationId"] = UserLogin.PowerStationID @@ -48,6 +40,6 @@ func powerStationIDJSON(UserLogin *types.LoginCredentials) ([]byte, error) { func GetDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { // Get the response and return any errors - resp, err := utils.MarshalStructToJSON(&PowerstationOutputData) + resp, err := MarshalStructToJSON(&PowerstationOutputData) return resp, err } diff --git a/utils/paramcheck.go b/cmd/gogoodwe/utils/paramcheck.go similarity index 100% rename from utils/paramcheck.go rename to cmd/gogoodwe/utils/paramcheck.go diff --git a/utils/responsehandler.go b/cmd/gogoodwe/utils/responsehandler.go similarity index 100% rename from utils/responsehandler.go rename to cmd/gogoodwe/utils/responsehandler.go From 2bf06f655d444f74027df4784402f5acc50294a8 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 10:20:45 +1100 Subject: [PATCH 05/26] refactor --- cmd/gogoodwe/inverter/fetchdata.go | 2 +- cmd/gogoodwe/main.go | 1 - test/constants_test.go | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/gogoodwe/inverter/fetchdata.go b/cmd/gogoodwe/inverter/fetchdata.go index 4471656..c0a4561 100644 --- a/cmd/gogoodwe/inverter/fetchdata.go +++ b/cmd/gogoodwe/inverter/fetchdata.go @@ -8,9 +8,9 @@ import ( "errors" "fmt" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/login" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/login" "github.com/logrusorgru/aurora" ) diff --git a/cmd/gogoodwe/main.go b/cmd/gogoodwe/main.go index ebee618..6d5412e 100644 --- a/cmd/gogoodwe/main.go +++ b/cmd/gogoodwe/main.go @@ -18,7 +18,6 @@ func main() { if err != nil { panic(err) - os.Exit(1) } os.Exit(0) } diff --git a/test/constants_test.go b/test/constants_test.go index 5b95341..d877405 100644 --- a/test/constants_test.go +++ b/test/constants_test.go @@ -3,7 +3,7 @@ package testing import ( "testing" - "github.com/AaronSaikovski/gogoodwe/constants" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" ) // TestAuthLoginUrl Test Auth Login Url From d900fbf96932980bf0c2a548b9c6d069705f6f71 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 10:32:04 +1100 Subject: [PATCH 06/26] refactored pkg --- cmd/gogoodwe/app/app.go | 4 ++-- cmd/gogoodwe/{inverter => powerstation}/fetchdata.go | 6 +++--- cmd/gogoodwe/{inverter => powerstation}/inverter.go | 2 +- cmd/gogoodwe/{login => semsapi}/apilogin.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename cmd/gogoodwe/{inverter => powerstation}/fetchdata.go (90%) rename cmd/gogoodwe/{inverter => powerstation}/inverter.go (98%) rename cmd/gogoodwe/{login => semsapi}/apilogin.go (89%) diff --git a/cmd/gogoodwe/app/app.go b/cmd/gogoodwe/app/app.go index 5c5cca8..dbadd0b 100644 --- a/cmd/gogoodwe/app/app.go +++ b/cmd/gogoodwe/app/app.go @@ -2,7 +2,7 @@ package app // Main package - This is the main program entry point import ( - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/inverter" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/powerstation" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" "github.com/alexflint/go-arg" ) @@ -25,5 +25,5 @@ func Run() error { } // Get the data from the API, return any errors. Pass in args as string - return inverter.FetchData(args.Account, args.Password, args.PowerStationID) + return powerstation.FetchData(args.Account, args.Password, args.PowerStationID) } diff --git a/cmd/gogoodwe/inverter/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go similarity index 90% rename from cmd/gogoodwe/inverter/fetchdata.go rename to cmd/gogoodwe/powerstation/fetchdata.go index c0a4561..c96eb63 100644 --- a/cmd/gogoodwe/inverter/fetchdata.go +++ b/cmd/gogoodwe/powerstation/fetchdata.go @@ -2,13 +2,13 @@ # Name: data - fetches data from the goodwe API - and processes it to pass back to caller # Author: Aaron Saikovski - asaikovski@outlook.com */ -package inverter +package powerstation import ( "errors" "fmt" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/login" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/semsapi" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" "github.com/logrusorgru/aurora" @@ -29,7 +29,7 @@ func FetchData(Account string, Password string, PowerStationID string) error { } // Do the login..check for errors - err := login.ApiLogin(&SemsUserLogin, &SemsResponseData) + err := semsapi.ApiLogin(&SemsUserLogin, &SemsResponseData) if err == nil { // Fetch the data diff --git a/cmd/gogoodwe/inverter/inverter.go b/cmd/gogoodwe/powerstation/inverter.go similarity index 98% rename from cmd/gogoodwe/inverter/inverter.go rename to cmd/gogoodwe/powerstation/inverter.go index bb7fa72..0eb6a4b 100644 --- a/cmd/gogoodwe/inverter/inverter.go +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -2,7 +2,7 @@ # Name: inverter - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" # Author: Aaron Saikovski - asaikovski@outlook.com */ -package inverter +package powerstation import ( "bytes" diff --git a/cmd/gogoodwe/login/apilogin.go b/cmd/gogoodwe/semsapi/apilogin.go similarity index 89% rename from cmd/gogoodwe/login/apilogin.go rename to cmd/gogoodwe/semsapi/apilogin.go index 4ffba73..ad80886 100644 --- a/cmd/gogoodwe/login/apilogin.go +++ b/cmd/gogoodwe/semsapi/apilogin.go @@ -2,7 +2,7 @@ # Name: apiLogin - Logs in to the SEMS API # Author: Aaron Saikovski - asaikovski@outlook.com */ -package login +package semsapi import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/authentication" @@ -10,7 +10,7 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -// apiLogin - Login to the API +// SemsApiLogin - Login to the GoodWe Sems Portal API func ApiLogin(UserLoginCreds *types.LoginCredentials, UserLoginResponse *types.LoginResponse) error { // Do the login - update the pointer to the struct SemsResponseData From f3652ad02c6e5a42ff435360cc52e88eeae88d60 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 11:32:21 +1100 Subject: [PATCH 07/26] refactored structs and functions --- cmd/gogoodwe/authentication/authentication.go | 52 ++++++++++++++++ cmd/gogoodwe/powerstation/fetchdata.go | 62 +++++++++++++++++-- cmd/gogoodwe/powerstation/inverter.go | 62 +++++++++++++++++-- cmd/gogoodwe/semsapi/apilogin.go | 12 ++++ cmd/gogoodwe/types/logindata.go | 10 +++ 5 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 cmd/gogoodwe/types/logindata.go diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go index c498946..c801986 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -70,3 +70,55 @@ func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCreden return nil } + +func DoLoginv2(UserLoginFlow *types.LoginDataFlow) error { + //check if the UserLogin struct is empty + if usererr := checkUserLoginInfo(UserLoginFlow.LoginCreds); usererr != nil { + return usererr + } + + // User login struct to be converted to JSON + jsonData, jsonErr := utils.MarshalStructToJSON(UserLoginFlow.LoginCreds) + if jsonErr != nil { + return jsonErr + } + + // Create a new http request + req, err := http.NewRequest(http.MethodPost, constants.AuthLoginUrl, bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + + //Add headers pass in the pointer to set the headers on the request object + setHeaders(req) + + //make the API Call + client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + + //cleanup + defer resp.Body.Close() + + // Get the response body + respBody, respErr := utils.FetchResponseBody(resp.Body) + if respErr != nil { + return respErr + } + + //marshall response to SemsRespInfo struct + dataErr := utils.UnmarshalDataToStruct(respBody, &UserLoginFlow.LoginResp) + if dataErr != nil { + return dataErr + } + + // check for successful login return value..return a login error + loginErr := checkUserLoginResponse(UserLoginFlow.LoginResp.Msg) + if loginErr != nil { + return loginErr + } + + return nil +} diff --git a/cmd/gogoodwe/powerstation/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go index c96eb63..d66b639 100644 --- a/cmd/gogoodwe/powerstation/fetchdata.go +++ b/cmd/gogoodwe/powerstation/fetchdata.go @@ -14,26 +14,78 @@ import ( "github.com/logrusorgru/aurora" ) +//Old ver // FetchData - Main API fetch function +// func FetchData(Account string, Password string, PowerStationID string) error { + +// // Data types +// var SemsResponseData types.LoginResponse +// var PowerstationData types.InverterData + +// // Create a new SemsLoginCreds object via a struct literal +// var SemsUserLogin = types.LoginCredentials{ +// Account: Account, +// Password: Password, +// PowerStationID: PowerStationID, +// } + +// // Do the login..check for errors +// err := semsapi.ApiLogin(&SemsUserLogin, &SemsResponseData) +// if err == nil { + +// // Fetch the data +// dataerr := fetchInverterData(&SemsResponseData, &SemsUserLogin, &PowerstationData) +// if dataerr != nil { +// utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) +// return dataerr +// } else { +// // Get output +// dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) +// if jsonerr != nil { +// utils.HandleError(errors.New("error: converting powerstation data")) +// return jsonerr + +// } else { +// //Display output +// fmt.Println(aurora.BrightYellow(string(dataOutput))) +// } +// } + +// } else { +// utils.HandleError(err) +// return err +// } + +// return nil +// } + func FetchData(Account string, Password string, PowerStationID string) error { // Data types - var SemsResponseData types.LoginResponse var PowerstationData types.InverterData - // Create a new SemsLoginCreds object via a struct literal - var SemsUserLogin = types.LoginCredentials{ + // User account struct + creds := &types.LoginCredentials{ Account: Account, Password: Password, PowerStationID: PowerStationID, } + // Login API Response object + resp := &types.LoginResponse{} + + // Create a new LoginDataFlow object reference + LoginDataFlow := &types.LoginDataFlow{ + LoginCreds: creds, + LoginResp: resp, + } + // Do the login..check for errors - err := semsapi.ApiLogin(&SemsUserLogin, &SemsResponseData) + err := semsapi.ApiLoginV2(LoginDataFlow) if err == nil { // Fetch the data - dataerr := fetchInverterData(&SemsResponseData, &SemsUserLogin, &PowerstationData) + dataerr := fetchInverterData(LoginDataFlow, &PowerstationData) if dataerr != nil { utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) return dataerr diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go index 0eb6a4b..5d98ba1 100644 --- a/cmd/gogoodwe/powerstation/inverter.go +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -14,23 +14,77 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) +//old ver // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using theSEMs API -func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials, PowerstationOutputData *types.InverterData) error { +// func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials, PowerstationOutputData *types.InverterData) error { + +// // get the Token header data +// tokenMapJSONData, err := utils.DataTokenJSON(SemsResponseData) +// if err != nil { +// return err +// } + +// // get the Powerstation ID header data +// powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLogin) +// if err != nil { +// return err +// } + +// //Get the url from the Auth API and append the data url part +// url := SemsResponseData.API + constants.PowerStationURL + +// // Create a new http request +// req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) +// if err != nil { +// return err +// } + +// //Add headers pass in the pointer to set the headers on the request object +// utils.SetHeaders(req, tokenMapJSONData) + +// //make the API Call +// client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} +// resp, err := client.Do(req) +// if err != nil { +// return err +// } + +// //cleanup +// defer resp.Body.Close() + +// // Get the response body +// respBody, err := utils.FetchResponseBody(resp.Body) +// if err != nil { +// return err +// } + +// //marshall response to SemsRespInfo struct +// dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) +// if dataStructErr != nil { +// return dataStructErr +// } + +// return nil + +// } + +// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API +func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputData *types.InverterData) error { // get the Token header data - tokenMapJSONData, err := utils.DataTokenJSON(SemsResponseData) + tokenMapJSONData, err := utils.DataTokenJSON(UserLoginFlow.LoginResp) if err != nil { return err } // get the Powerstation ID header data - powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLogin) + powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLoginFlow.LoginCreds) if err != nil { return err } //Get the url from the Auth API and append the data url part - url := SemsResponseData.API + constants.PowerStationURL + url := UserLoginFlow.LoginResp.API + constants.PowerStationURL // Create a new http request req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) diff --git a/cmd/gogoodwe/semsapi/apilogin.go b/cmd/gogoodwe/semsapi/apilogin.go index ad80886..feede58 100644 --- a/cmd/gogoodwe/semsapi/apilogin.go +++ b/cmd/gogoodwe/semsapi/apilogin.go @@ -22,3 +22,15 @@ func ApiLogin(UserLoginCreds *types.LoginCredentials, UserLoginResponse *types.L return nil } } + +func ApiLoginV2(UserLoginFlow *types.LoginDataFlow) error { + + // Do the login - update the pointer to the struct SemsResponseData + err := authentication.DoLoginv2(UserLoginFlow) + if err != nil { + utils.HandleError(err) + return err + } else { + return nil + } +} diff --git a/cmd/gogoodwe/types/logindata.go b/cmd/gogoodwe/types/logindata.go new file mode 100644 index 0000000..2be2a26 --- /dev/null +++ b/cmd/gogoodwe/types/logindata.go @@ -0,0 +1,10 @@ +package types + +/* +# Name: LoginDataFlow - Struct to hold pointers to User login data structs +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +type LoginDataFlow struct { + LoginCreds *LoginCredentials + LoginResp *LoginResponse +} From c951760aac08bafb90015bb0a429cab483eaed3b Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 11:44:07 +1100 Subject: [PATCH 08/26] removed constants --- cmd/gogoodwe/authentication/authentication.go | 20 ++++-- cmd/gogoodwe/authentication/authutils.go | 3 +- cmd/gogoodwe/constants/constants.go | 23 ------- cmd/gogoodwe/powerstation/fetchdata.go | 46 +------------- cmd/gogoodwe/powerstation/inverter.go | 63 +++---------------- cmd/gogoodwe/utils/args.go | 7 ++- test/constants_test.go | 51 --------------- 7 files changed, 30 insertions(+), 183 deletions(-) delete mode 100644 cmd/gogoodwe/constants/constants.go delete mode 100644 test/constants_test.go diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go index c801986..4456180 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -10,11 +10,21 @@ import ( "net/http" "time" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) +var ( + // Auth Login Url + AuthLoginUrl string = "https://www.semsportal.com/api/v2/Common/CrossLogin" + + // Default timeout value + HTTPTimeout int = 20 + + //API login success response message + SemsLoginSuccessResponse string = "Successful" +) + // DoLogin - Main public login function // Logs into the SEMs API func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials) error { @@ -31,7 +41,7 @@ func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCreden } // Create a new http request - req, err := http.NewRequest(http.MethodPost, constants.AuthLoginUrl, bytes.NewBuffer(jsonData)) + req, err := http.NewRequest(http.MethodPost, AuthLoginUrl, bytes.NewBuffer(jsonData)) if err != nil { return err } @@ -40,7 +50,7 @@ func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCreden setHeaders(req) //make the API Call - client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} + client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} resp, err := client.Do(req) if err != nil { return err @@ -84,7 +94,7 @@ func DoLoginv2(UserLoginFlow *types.LoginDataFlow) error { } // Create a new http request - req, err := http.NewRequest(http.MethodPost, constants.AuthLoginUrl, bytes.NewBuffer(jsonData)) + req, err := http.NewRequest(http.MethodPost, AuthLoginUrl, bytes.NewBuffer(jsonData)) if err != nil { return err } @@ -93,7 +103,7 @@ func DoLoginv2(UserLoginFlow *types.LoginDataFlow) error { setHeaders(req) //make the API Call - client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} + client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} resp, err := client.Do(req) if err != nil { return err diff --git a/cmd/gogoodwe/authentication/authutils.go b/cmd/gogoodwe/authentication/authutils.go index e3ab4d2..e8ea959 100644 --- a/cmd/gogoodwe/authentication/authutils.go +++ b/cmd/gogoodwe/authentication/authutils.go @@ -10,7 +10,6 @@ import ( "net/http" "strings" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" ) @@ -31,7 +30,7 @@ func checkUserLoginInfo(UserLogin *types.LoginCredentials) error { // CheckUserLoginResponse - check for successful login return value..return a login error func checkUserLoginResponse(loginResponse string) error { - if strings.Compare(loginResponse, constants.SemsLoginSuccessResponse) != 0 { + if strings.Compare(loginResponse, "Successful") != 0 { return errors.New("**API Login Error: " + loginResponse) } else { return nil diff --git a/cmd/gogoodwe/constants/constants.go b/cmd/gogoodwe/constants/constants.go deleted file mode 100644 index 70ba5dd..0000000 --- a/cmd/gogoodwe/constants/constants.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -# Name: constants - shared constants -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ - -package constants - -const ( - // Auth Login Url - AuthLoginUrl string = "https://www.semsportal.com/api/v2/Common/CrossLogin" - - // Powerstation API Url - PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" - - // Default timeout value - HTTPTimeout = 20 - - // Version string - VersionString string = "gogoodwe v2.0.0" - - //API login success response message - SemsLoginSuccessResponse string = "Successful" -) diff --git a/cmd/gogoodwe/powerstation/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go index d66b639..a248fd2 100644 --- a/cmd/gogoodwe/powerstation/fetchdata.go +++ b/cmd/gogoodwe/powerstation/fetchdata.go @@ -14,51 +14,7 @@ import ( "github.com/logrusorgru/aurora" ) -//Old ver -// FetchData - Main API fetch function -// func FetchData(Account string, Password string, PowerStationID string) error { - -// // Data types -// var SemsResponseData types.LoginResponse -// var PowerstationData types.InverterData - -// // Create a new SemsLoginCreds object via a struct literal -// var SemsUserLogin = types.LoginCredentials{ -// Account: Account, -// Password: Password, -// PowerStationID: PowerStationID, -// } - -// // Do the login..check for errors -// err := semsapi.ApiLogin(&SemsUserLogin, &SemsResponseData) -// if err == nil { - -// // Fetch the data -// dataerr := fetchInverterData(&SemsResponseData, &SemsUserLogin, &PowerstationData) -// if dataerr != nil { -// utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) -// return dataerr -// } else { -// // Get output -// dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) -// if jsonerr != nil { -// utils.HandleError(errors.New("error: converting powerstation data")) -// return jsonerr - -// } else { -// //Display output -// fmt.Println(aurora.BrightYellow(string(dataOutput))) -// } -// } - -// } else { -// utils.HandleError(err) -// return err -// } - -// return nil -// } - +// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API func FetchData(Account string, Password string, PowerStationID string) error { // Data types diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go index 5d98ba1..7ae65f0 100644 --- a/cmd/gogoodwe/powerstation/inverter.go +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -9,64 +9,17 @@ import ( "net/http" "time" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -//old ver -// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using theSEMs API -// func fetchInverterData(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials, PowerstationOutputData *types.InverterData) error { +var ( + // Powerstation API Url + PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" -// // get the Token header data -// tokenMapJSONData, err := utils.DataTokenJSON(SemsResponseData) -// if err != nil { -// return err -// } - -// // get the Powerstation ID header data -// powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLogin) -// if err != nil { -// return err -// } - -// //Get the url from the Auth API and append the data url part -// url := SemsResponseData.API + constants.PowerStationURL - -// // Create a new http request -// req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) -// if err != nil { -// return err -// } - -// //Add headers pass in the pointer to set the headers on the request object -// utils.SetHeaders(req, tokenMapJSONData) - -// //make the API Call -// client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} -// resp, err := client.Do(req) -// if err != nil { -// return err -// } - -// //cleanup -// defer resp.Body.Close() - -// // Get the response body -// respBody, err := utils.FetchResponseBody(resp.Body) -// if err != nil { -// return err -// } - -// //marshall response to SemsRespInfo struct -// dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) -// if dataStructErr != nil { -// return dataStructErr -// } - -// return nil - -// } + // Default timeout value + HTTPTimeout int = 20 +) // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputData *types.InverterData) error { @@ -84,7 +37,7 @@ func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputDat } //Get the url from the Auth API and append the data url part - url := UserLoginFlow.LoginResp.API + constants.PowerStationURL + url := (UserLoginFlow.LoginResp.API + PowerStationURL) // Create a new http request req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) @@ -96,7 +49,7 @@ func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputDat utils.SetHeaders(req, tokenMapJSONData) //make the API Call - client := &http.Client{Timeout: constants.HTTPTimeout * time.Second} + client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} resp, err := client.Do(req) if err != nil { return err diff --git a/cmd/gogoodwe/utils/args.go b/cmd/gogoodwe/utils/args.go index 8260ef2..ff9a304 100644 --- a/cmd/gogoodwe/utils/args.go +++ b/cmd/gogoodwe/utils/args.go @@ -1,6 +1,9 @@ package utils -import "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" +var ( + // Version string + VersionString string = "gogoodwe v2.0.0" +) // Args - struct using go-arg- https://github.com/alexflint/go-arg type Args struct { @@ -16,5 +19,5 @@ func (Args) Description() string { // Version - Version info func (Args) Version() string { - return constants.VersionString + return VersionString } diff --git a/test/constants_test.go b/test/constants_test.go deleted file mode 100644 index d877405..0000000 --- a/test/constants_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package testing - -import ( - "testing" - - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/constants" -) - -// TestAuthLoginUrl Test Auth Login Url -func TestAuthLoginUrl(t *testing.T) { - - AuthLoginUrlExpected := "https://www.semsportal.com/api/v2/Common/CrossLogin" - if constants.AuthLoginUrl != AuthLoginUrlExpected { - t.Errorf("AuthLoginUrl Const expected '%s' but got '%s'", AuthLoginUrlExpected, constants.AuthLoginUrl) - } -} - -// TestPowerStationURL Test Powerstation API Url -func TestPowerStationURL(t *testing.T) { - - PowerStationURLExpected := "v2/PowerStation/GetMonitorDetailByPowerstationId" - if constants.PowerStationURL != PowerStationURLExpected { - t.Errorf("PowerStationURL const expected '%s' but got '%s'", PowerStationURLExpected, constants.PowerStationURL) - } -} - -// HTTPTimeout Test HTTPTimeout value -// func TestHTTPTimeout(t *testing.T) { - -// HTTPTimeoutPowerStationURLExpected = 20 - -// PowerStationURLExpected := "v2/PowerStation/GetMonitorDetailByPowerstationId" -// if constants.HTTPTimeout != PowerStationURLExpected { -// t.Errorf("PowerStationURL const expected '%d' but got '%d'", PowerStationURLExpected, constants.PowerStationURL) -// } -// } - -// Default timeout value - -//API login success response message -//SemsLoginSuccessResponse string = "Successful" - -// TestSemsLoginSuccessResponse Test SemsLoginSuccessResponse test -func TestSemsLoginSuccessResponse(t *testing.T) { - - SemsLoginSuccessResponseExpected := "Successful" - - if constants.SemsLoginSuccessResponse != SemsLoginSuccessResponseExpected { - t.Errorf("SemsLoginSuccessResponse const expected '%s' but got '%s'", SemsLoginSuccessResponseExpected, constants.SemsLoginSuccessResponse) - } -} From a2ca81936c2f77e4d8ff04c36d43d4e6f9848f54 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 11:50:57 +1100 Subject: [PATCH 09/26] fixed refactor --- cmd/gogoodwe/authentication/authentication.go | 63 +------------------ cmd/gogoodwe/powerstation/fetchdata.go | 2 +- cmd/gogoodwe/semsapi/apilogin.go | 18 +----- 3 files changed, 7 insertions(+), 76 deletions(-) diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go index 4456180..503419c 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -1,5 +1,5 @@ /* -# Name: authentication - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin +# Name: DoLogin - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin # Author: Aaron Saikovski - asaikovski@outlook.com */ @@ -20,68 +20,11 @@ var ( // Default timeout value HTTPTimeout int = 20 - - //API login success response message - SemsLoginSuccessResponse string = "Successful" ) -// DoLogin - Main public login function -// Logs into the SEMs API -func DoLogin(SemsResponseData *types.LoginResponse, UserLogin *types.LoginCredentials) error { - - //check if the UserLogin struct is empty - if usererr := checkUserLoginInfo(UserLogin); usererr != nil { - return usererr - } - - // User login struct to be converted to JSON - jsonData, jsonErr := utils.MarshalStructToJSON(UserLogin) - if jsonErr != nil { - return jsonErr - } - - // Create a new http request - req, err := http.NewRequest(http.MethodPost, AuthLoginUrl, bytes.NewBuffer(jsonData)) - if err != nil { - return err - } - - //Add headers pass in the pointer to set the headers on the request object - setHeaders(req) - - //make the API Call - client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} - resp, err := client.Do(req) - if err != nil { - return err - } - - //cleanup - defer resp.Body.Close() - - // Get the response body - respBody, respErr := utils.FetchResponseBody(resp.Body) - if respErr != nil { - return respErr - } - - //marshall response to SemsRespInfo struct - dataErr := utils.UnmarshalDataToStruct(respBody, &SemsResponseData) - if dataErr != nil { - return dataErr - } - - // check for successful login return value..return a login error - loginErr := checkUserLoginResponse(SemsResponseData.Msg) - if loginErr != nil { - return loginErr - } - - return nil - -} +// Login - Login to the SEMS API +func DoLogin(UserLoginFlow *types.LoginDataFlow) error { -func DoLoginv2(UserLoginFlow *types.LoginDataFlow) error { //check if the UserLogin struct is empty if usererr := checkUserLoginInfo(UserLoginFlow.LoginCreds); usererr != nil { return usererr diff --git a/cmd/gogoodwe/powerstation/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go index a248fd2..9b4701e 100644 --- a/cmd/gogoodwe/powerstation/fetchdata.go +++ b/cmd/gogoodwe/powerstation/fetchdata.go @@ -37,7 +37,7 @@ func FetchData(Account string, Password string, PowerStationID string) error { } // Do the login..check for errors - err := semsapi.ApiLoginV2(LoginDataFlow) + err := semsapi.ApiLogin(LoginDataFlow) if err == nil { // Fetch the data diff --git a/cmd/gogoodwe/semsapi/apilogin.go b/cmd/gogoodwe/semsapi/apilogin.go index feede58..5c091f4 100644 --- a/cmd/gogoodwe/semsapi/apilogin.go +++ b/cmd/gogoodwe/semsapi/apilogin.go @@ -10,23 +10,11 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -// SemsApiLogin - Login to the GoodWe Sems Portal API -func ApiLogin(UserLoginCreds *types.LoginCredentials, UserLoginResponse *types.LoginResponse) error { +// ApiLogin - Login to the SEMS API +func ApiLogin(UserLoginFlow *types.LoginDataFlow) error { // Do the login - update the pointer to the struct SemsResponseData - err := authentication.DoLogin(UserLoginResponse, UserLoginCreds) - if err != nil { - utils.HandleError(err) - return err - } else { - return nil - } -} - -func ApiLoginV2(UserLoginFlow *types.LoginDataFlow) error { - - // Do the login - update the pointer to the struct SemsResponseData - err := authentication.DoLoginv2(UserLoginFlow) + err := authentication.DoLogin(UserLoginFlow) if err != nil { utils.HandleError(err) return err From b31fd4d6138a2824216af2a0cbc0bba6061c897c Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 12:59:02 +1100 Subject: [PATCH 10/26] fixed refactor --- cmd/gogoodwe/powerstation/fetchdata.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gogoodwe/powerstation/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go index 9b4701e..16b7950 100644 --- a/cmd/gogoodwe/powerstation/fetchdata.go +++ b/cmd/gogoodwe/powerstation/fetchdata.go @@ -17,8 +17,8 @@ import ( // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API func FetchData(Account string, Password string, PowerStationID string) error { - // Data types - var PowerstationData types.InverterData + // Powerstation Output Data + PowerstationData := types.InverterData{} // User account struct creds := &types.LoginCredentials{ From 84822ef44dcf1fcd0e173df5452495619af308a1 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 13:06:03 +1100 Subject: [PATCH 11/26] fixed refactor --- test/{samplestruct_test.go => struct_test.go} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename test/{samplestruct_test.go => struct_test.go} (59%) diff --git a/test/samplestruct_test.go b/test/struct_test.go similarity index 59% rename from test/samplestruct_test.go rename to test/struct_test.go index d34e611..2add9b8 100644 --- a/test/samplestruct_test.go +++ b/test/struct_test.go @@ -5,15 +5,15 @@ A Sample test harness. package testing // import ( -// "github.com/AaronSaikovski/gostarter/internal/app/types" +// "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" // "testing" // ) // // A testing function. -// func TestSampleStructString(t *testing.T) { +// func TestLoginCredentials(t *testing.T) { // expected := "test data" -// ateststruct := types.Sample{SampleString: "test data", SampleInt: 1} +// ateststruct := types.LoginCredentials{SampleString: "test data", SampleInt: 1} // if ateststruct.SampleString != expected { // t.Errorf("struct expected '%s' but got '%s'", expected, ateststruct.SampleString) From 36a38c73771d51603a59b0d2b817b542779a439c Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 13:08:54 +1100 Subject: [PATCH 12/26] fixed refactor --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d9720..a47cd84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ## v2.0.0 (2024-01-22) -- Major refactoring to cleanup project structure -- Simplified package structure +- Major refactoring to cleanup project structure. +- Simplified package structure. +- refactored structs to use pointers more efficiently. ## v1.4.0 (2023-08-30) From 9a7108b3b90f19dae89d61cd7af5520766646a90 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 13:34:22 +1100 Subject: [PATCH 13/26] refactored utils --- cmd/gogoodwe/utils/jsonutils.go | 14 +++++++++++++- cmd/gogoodwe/utils/response.go | 11 +++++++++++ cmd/gogoodwe/utils/responsehandler.go | 24 ------------------------ 3 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 cmd/gogoodwe/utils/response.go delete mode 100644 cmd/gogoodwe/utils/responsehandler.go diff --git a/cmd/gogoodwe/utils/jsonutils.go b/cmd/gogoodwe/utils/jsonutils.go index 71fc457..fc233a2 100644 --- a/cmd/gogoodwe/utils/jsonutils.go +++ b/cmd/gogoodwe/utils/jsonutils.go @@ -1,5 +1,5 @@ /* -# Name: powerstationhelper - helper functions to get the Powerstation Data from the API +# Name: jsonutils - helper functions to get the Powerstation Data from the API # Author: Aaron Saikovski - asaikovski@outlook.com */ package utils @@ -43,3 +43,15 @@ func GetDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { resp, err := MarshalStructToJSON(&PowerstationOutputData) return resp, err } + +// UnmarshalDataToStruct - Unmarshall http response to target struct +func UnmarshalDataToStruct(respBody []byte, targetStruct interface{}) error { + resperr := json.Unmarshal(respBody, &targetStruct) + return resperr +} + +// MarshalStructToJSON - Marshall the struct pointer to JSON +func MarshalStructToJSON(targetStruct interface{}) ([]byte, error) { + jsonData, err := json.Marshal(&targetStruct) + return jsonData, err +} diff --git a/cmd/gogoodwe/utils/response.go b/cmd/gogoodwe/utils/response.go new file mode 100644 index 0000000..5b93e20 --- /dev/null +++ b/cmd/gogoodwe/utils/response.go @@ -0,0 +1,11 @@ +package utils + +import ( + "io" +) + +// FetchResponseBody - Get the response body from a HTTP response +func FetchResponseBody(resp io.Reader) ([]byte, error) { + respBody, err := io.ReadAll(resp) + return respBody, err +} diff --git a/cmd/gogoodwe/utils/responsehandler.go b/cmd/gogoodwe/utils/responsehandler.go deleted file mode 100644 index 7f06c10..0000000 --- a/cmd/gogoodwe/utils/responsehandler.go +++ /dev/null @@ -1,24 +0,0 @@ -package utils - -import ( - "encoding/json" - "io" -) - -// FetchResponseBody - Get the response body from a HTTP response -func FetchResponseBody(resp io.Reader) ([]byte, error) { - respBody, err := io.ReadAll(resp) - return respBody, err -} - -// UnmarshalDataToStruct - Unmarshall http response to target struct -func UnmarshalDataToStruct(respBody []byte, targetStruct interface{}) error { - resperr := json.Unmarshal(respBody, &targetStruct) - return resperr -} - -// MarshalStructToJSON - Marshall the struct pointer to JSON -func MarshalStructToJSON(targetStruct interface{}) ([]byte, error) { - jsonData, err := json.Marshal(&targetStruct) - return jsonData, err -} From 327f8b259b35c5d4d91c3f5e2d4a58d72cc2a5b2 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 13:52:23 +1100 Subject: [PATCH 14/26] refactored powerstation --- cmd/gogoodwe/powerstation/{inverter.go => powerstation.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/gogoodwe/powerstation/{inverter.go => powerstation.go} (100%) diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/powerstation.go similarity index 100% rename from cmd/gogoodwe/powerstation/inverter.go rename to cmd/gogoodwe/powerstation/powerstation.go From ce6b65cf987db9013c7ae2299267d18a5a0c95db Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 14:12:11 +1100 Subject: [PATCH 15/26] updated makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 70ed7b3..4e763a6 100644 --- a/Makefile +++ b/Makefile @@ -47,6 +47,7 @@ run: .PHONY: clean ## clean - Remove the old builds and any debug information clean: + go clean -cache go clean rm -rf dist rm bin/${TARGET} From 9c61d7421fbede59c7ac18385bfe1a656ea857f6 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Tue, 6 Feb 2024 14:16:37 +1100 Subject: [PATCH 16/26] updated modfile --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fcddca2..b544212 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/AaronSaikovski/gogoodwe -go 1.21 +go 1.21.6 require ( github.com/alexflint/go-arg v1.4.3 From 76166d104ed44ade5d7788c82e3b7c1f54c681bb Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Wed, 7 Feb 2024 10:25:05 +1100 Subject: [PATCH 17/26] updated to 1.22.0 --- Makefile | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 4e763a6..5e177ad 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ staticcheck: .PHONY: seccheck ## seccheck - Code vulnerability check seccheck: - govulncheck ./... + #govulncheck ./... .PHONY: lint diff --git a/go.mod b/go.mod index b544212..b7f56b9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/AaronSaikovski/gogoodwe -go 1.21.6 +go 1.22.0 require ( github.com/alexflint/go-arg v1.4.3 From ca26799708e729c22a7f6386914759cb8b3e73e1 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Fri, 9 Feb 2024 15:13:31 +1100 Subject: [PATCH 18/26] minor refactor --- .github/workflows/build.yml | 2 +- cmd/gogoodwe/powerstation/fetchdata.go | 67 ---------------- cmd/gogoodwe/powerstation/inverter.go | 75 ++++++++++++++++++ cmd/gogoodwe/powerstation/powerstation.go | 94 +++++++++++------------ 4 files changed, 119 insertions(+), 119 deletions(-) delete mode 100644 cmd/gogoodwe/powerstation/fetchdata.go create mode 100644 cmd/gogoodwe/powerstation/inverter.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f091842..fca6055 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version: "1.22" - name: Verify dependencies run: go mod verify diff --git a/cmd/gogoodwe/powerstation/fetchdata.go b/cmd/gogoodwe/powerstation/fetchdata.go deleted file mode 100644 index 16b7950..0000000 --- a/cmd/gogoodwe/powerstation/fetchdata.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -# Name: data - fetches data from the goodwe API - and processes it to pass back to caller -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package powerstation - -import ( - "errors" - "fmt" - - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/semsapi" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" - "github.com/logrusorgru/aurora" -) - -// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API -func FetchData(Account string, Password string, PowerStationID string) error { - - // Powerstation Output Data - PowerstationData := types.InverterData{} - - // User account struct - creds := &types.LoginCredentials{ - Account: Account, - Password: Password, - PowerStationID: PowerStationID, - } - - // Login API Response object - resp := &types.LoginResponse{} - - // Create a new LoginDataFlow object reference - LoginDataFlow := &types.LoginDataFlow{ - LoginCreds: creds, - LoginResp: resp, - } - - // Do the login..check for errors - err := semsapi.ApiLogin(LoginDataFlow) - if err == nil { - - // Fetch the data - dataerr := fetchInverterData(LoginDataFlow, &PowerstationData) - if dataerr != nil { - utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) - return dataerr - } else { - // Get output - dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) - if jsonerr != nil { - utils.HandleError(errors.New("error: converting powerstation data")) - return jsonerr - - } else { - //Display output - fmt.Println(aurora.BrightYellow(string(dataOutput))) - } - } - - } else { - utils.HandleError(err) - return err - } - - return nil -} diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go new file mode 100644 index 0000000..7ae65f0 --- /dev/null +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -0,0 +1,75 @@ +/* +# Name: inverter - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package powerstation + +import ( + "bytes" + "net/http" + "time" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +var ( + // Powerstation API Url + PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" + + // Default timeout value + HTTPTimeout int = 20 +) + +// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API +func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputData *types.InverterData) error { + + // get the Token header data + tokenMapJSONData, err := utils.DataTokenJSON(UserLoginFlow.LoginResp) + if err != nil { + return err + } + + // get the Powerstation ID header data + powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLoginFlow.LoginCreds) + if err != nil { + return err + } + + //Get the url from the Auth API and append the data url part + url := (UserLoginFlow.LoginResp.API + PowerStationURL) + + // Create a new http request + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) + if err != nil { + return err + } + + //Add headers pass in the pointer to set the headers on the request object + utils.SetHeaders(req, tokenMapJSONData) + + //make the API Call + client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + + //cleanup + defer resp.Body.Close() + + // Get the response body + respBody, err := utils.FetchResponseBody(resp.Body) + if err != nil { + return err + } + + //marshall response to SemsRespInfo struct + dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) + if dataStructErr != nil { + return dataStructErr + } + + return nil + +} diff --git a/cmd/gogoodwe/powerstation/powerstation.go b/cmd/gogoodwe/powerstation/powerstation.go index 7ae65f0..16b7950 100644 --- a/cmd/gogoodwe/powerstation/powerstation.go +++ b/cmd/gogoodwe/powerstation/powerstation.go @@ -1,75 +1,67 @@ /* -# Name: inverter - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" +# Name: data - fetches data from the goodwe API - and processes it to pass back to caller # Author: Aaron Saikovski - asaikovski@outlook.com */ package powerstation import ( - "bytes" - "net/http" - "time" + "errors" + "fmt" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/semsapi" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" -) - -var ( - // Powerstation API Url - PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" - - // Default timeout value - HTTPTimeout int = 20 + "github.com/logrusorgru/aurora" ) // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API -func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputData *types.InverterData) error { +func FetchData(Account string, Password string, PowerStationID string) error { - // get the Token header data - tokenMapJSONData, err := utils.DataTokenJSON(UserLoginFlow.LoginResp) - if err != nil { - return err - } + // Powerstation Output Data + PowerstationData := types.InverterData{} - // get the Powerstation ID header data - powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLoginFlow.LoginCreds) - if err != nil { - return err + // User account struct + creds := &types.LoginCredentials{ + Account: Account, + Password: Password, + PowerStationID: PowerStationID, } - //Get the url from the Auth API and append the data url part - url := (UserLoginFlow.LoginResp.API + PowerStationURL) + // Login API Response object + resp := &types.LoginResponse{} - // Create a new http request - req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) - if err != nil { - return err + // Create a new LoginDataFlow object reference + LoginDataFlow := &types.LoginDataFlow{ + LoginCreds: creds, + LoginResp: resp, } - //Add headers pass in the pointer to set the headers on the request object - utils.SetHeaders(req, tokenMapJSONData) - - //make the API Call - client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} - resp, err := client.Do(req) - if err != nil { + // Do the login..check for errors + err := semsapi.ApiLogin(LoginDataFlow) + if err == nil { + + // Fetch the data + dataerr := fetchInverterData(LoginDataFlow, &PowerstationData) + if dataerr != nil { + utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) + return dataerr + } else { + // Get output + dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) + if jsonerr != nil { + utils.HandleError(errors.New("error: converting powerstation data")) + return jsonerr + + } else { + //Display output + fmt.Println(aurora.BrightYellow(string(dataOutput))) + } + } + + } else { + utils.HandleError(err) return err } - //cleanup - defer resp.Body.Close() - - // Get the response body - respBody, err := utils.FetchResponseBody(resp.Body) - if err != nil { - return err - } - - //marshall response to SemsRespInfo struct - dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) - if dataStructErr != nil { - return dataStructErr - } - return nil - } From f346198b1d5d151b359667dd41a4b7223030d14f Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Fri, 9 Feb 2024 15:31:34 +1100 Subject: [PATCH 19/26] added consts --- cmd/gogoodwe/authentication/authentication.go | 19 ++++++++----------- cmd/gogoodwe/powerstation/inverter.go | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go index 503419c..7008eab 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -1,7 +1,7 @@ -/* -# Name: DoLogin - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ +// /* +// # Name: DoLogin - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin +// # Author: Aaron Saikovski - asaikovski@outlook.com +// */ package authentication @@ -14,12 +14,9 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -var ( - // Auth Login Url - AuthLoginUrl string = "https://www.semsportal.com/api/v2/Common/CrossLogin" - - // Default timeout value - HTTPTimeout int = 20 +const ( + AuthLoginURL = "https://www.semsportal.com/api/v2/Common/CrossLogin" + HTTPTimeout = 20 // seconds ) // Login - Login to the SEMS API @@ -37,7 +34,7 @@ func DoLogin(UserLoginFlow *types.LoginDataFlow) error { } // Create a new http request - req, err := http.NewRequest(http.MethodPost, AuthLoginUrl, bytes.NewBuffer(jsonData)) + req, err := http.NewRequest(http.MethodPost, AuthLoginURL, bytes.NewBuffer(jsonData)) if err != nil { return err } diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go index 7ae65f0..e406139 100644 --- a/cmd/gogoodwe/powerstation/inverter.go +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -13,7 +13,7 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -var ( +const ( // Powerstation API Url PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" From 4d17225a1a5c1a48a9f61f5960977f648610e88c Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Fri, 9 Feb 2024 16:00:51 +1100 Subject: [PATCH 20/26] big refactor --- cmd/gogoodwe/authentication/authutils.go | 8 ++-- cmd/gogoodwe/main.go | 20 ++++----- cmd/gogoodwe/powerstation/inverter.go | 2 +- cmd/gogoodwe/powerstation/powerstation.go | 52 ++++++++++------------- cmd/gogoodwe/semsapi/apilogin.go | 5 +-- cmd/gogoodwe/utils/jsonutils.go | 4 +- 6 files changed, 39 insertions(+), 52 deletions(-) diff --git a/cmd/gogoodwe/authentication/authutils.go b/cmd/gogoodwe/authentication/authutils.go index e8ea959..8a37c9b 100644 --- a/cmd/gogoodwe/authentication/authutils.go +++ b/cmd/gogoodwe/authentication/authutils.go @@ -21,18 +21,16 @@ func setHeaders(r *http.Request) { // CheckUserLoginInfo - Check user login struct is valid/not null func checkUserLoginInfo(UserLogin *types.LoginCredentials) error { - if (*UserLogin == types.LoginCredentials{}) { + if *UserLogin == (types.LoginCredentials{}) { return errors.New("**Error: User Login details are empty or invalid..**") - } else { - return nil } + return nil } // CheckUserLoginResponse - check for successful login return value..return a login error func checkUserLoginResponse(loginResponse string) error { if strings.Compare(loginResponse, "Successful") != 0 { return errors.New("**API Login Error: " + loginResponse) - } else { - return nil } + return nil } diff --git a/cmd/gogoodwe/main.go b/cmd/gogoodwe/main.go index 6d5412e..a418908 100644 --- a/cmd/gogoodwe/main.go +++ b/cmd/gogoodwe/main.go @@ -1,23 +1,19 @@ /* -# Name: GoGoodwe - Authenticates to and queries the SEMS Solar inverter API -# Author: Aaron Saikovski - asaikovski@outlook.com +Package main implements a program that authenticates to and queries the SEMS Solar inverter API. */ package main import ( - "os" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/app" + "log" ) -// main - program main func main() { - - //setup and run app - err := app.Run() - - if err != nil { - panic(err) + if err := runApp(); err != nil { + log.Fatalf("error: %v", err) } - os.Exit(0) +} + +func runApp() error { + return app.Run() } diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go index e406139..7532a8a 100644 --- a/cmd/gogoodwe/powerstation/inverter.go +++ b/cmd/gogoodwe/powerstation/inverter.go @@ -31,7 +31,7 @@ func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputDat } // get the Powerstation ID header data - powerStationMapJSONData, err := utils.PowerStationIDJSON(UserLoginFlow.LoginCreds) + powerStationMapJSONData, err := utils.PowerStationIdJSON(UserLoginFlow.LoginCreds) if err != nil { return err } diff --git a/cmd/gogoodwe/powerstation/powerstation.go b/cmd/gogoodwe/powerstation/powerstation.go index 16b7950..36d10cf 100644 --- a/cmd/gogoodwe/powerstation/powerstation.go +++ b/cmd/gogoodwe/powerstation/powerstation.go @@ -17,9 +17,6 @@ import ( // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API func FetchData(Account string, Password string, PowerStationID string) error { - // Powerstation Output Data - PowerstationData := types.InverterData{} - // User account struct creds := &types.LoginCredentials{ Account: Account, @@ -27,41 +24,38 @@ func FetchData(Account string, Password string, PowerStationID string) error { PowerStationID: PowerStationID, } - // Login API Response object - resp := &types.LoginResponse{} - // Create a new LoginDataFlow object reference - LoginDataFlow := &types.LoginDataFlow{ + loginDataFlow := &types.LoginDataFlow{ LoginCreds: creds, - LoginResp: resp, + LoginResp: &types.LoginResponse{}, } // Do the login..check for errors - err := semsapi.ApiLogin(LoginDataFlow) - if err == nil { + err := semsapi.ApiLogin(loginDataFlow) + if err != nil { + utils.HandleError(err) + return err + } - // Fetch the data - dataerr := fetchInverterData(LoginDataFlow, &PowerstationData) - if dataerr != nil { - utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) - return dataerr - } else { - // Get output - dataOutput, jsonerr := utils.GetDataJSON(&PowerstationData) - if jsonerr != nil { - utils.HandleError(errors.New("error: converting powerstation data")) - return jsonerr + // Powerstation Output Data + powerstationData := types.InverterData{} - } else { - //Display output - fmt.Println(aurora.BrightYellow(string(dataOutput))) - } - } + // Fetch the data + fetchDataerr := fetchInverterData(loginDataFlow, &powerstationData) + if fetchDataerr != nil { + utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) + return fetchDataerr + } - } else { - utils.HandleError(err) - return err + // Get output + dataOutput, jsonerr := utils.GetDataJSON(&powerstationData) + if jsonerr != nil { + utils.HandleError(errors.New("error: converting powerstation data")) + return jsonerr } + //Display output + fmt.Println(aurora.BrightYellow(string(dataOutput))) + return nil } diff --git a/cmd/gogoodwe/semsapi/apilogin.go b/cmd/gogoodwe/semsapi/apilogin.go index 5c091f4..56990e2 100644 --- a/cmd/gogoodwe/semsapi/apilogin.go +++ b/cmd/gogoodwe/semsapi/apilogin.go @@ -17,8 +17,7 @@ func ApiLogin(UserLoginFlow *types.LoginDataFlow) error { err := authentication.DoLogin(UserLoginFlow) if err != nil { utils.HandleError(err) - return err - } else { - return nil } + + return err } diff --git a/cmd/gogoodwe/utils/jsonutils.go b/cmd/gogoodwe/utils/jsonutils.go index fc233a2..f95c2de 100644 --- a/cmd/gogoodwe/utils/jsonutils.go +++ b/cmd/gogoodwe/utils/jsonutils.go @@ -26,8 +26,8 @@ func DataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { return jsonStr, err } -// PowerStationIDJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string -func PowerStationIDJSON(UserLogin *types.LoginCredentials) ([]byte, error) { +// PowerStationIdJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string +func PowerStationIdJSON(UserLogin *types.LoginCredentials) ([]byte, error) { powerStationMap := make(map[string]string) powerStationMap["powerStationId"] = UserLogin.PowerStationID From 653835525a561305954ffb2074823e75db9e01e0 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Fri, 9 Feb 2024 17:59:35 +1100 Subject: [PATCH 21/26] added fastjson --- cmd/gogoodwe/authentication/authentication.go | 5 ----- cmd/gogoodwe/powerstation/powerstation.go | 18 +++++++++++++----- go.mod | 1 + go.sum | 2 ++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/authentication/authentication.go index 7008eab..a8e106e 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/authentication/authentication.go @@ -1,8 +1,3 @@ -// /* -// # Name: DoLogin - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin -// # Author: Aaron Saikovski - asaikovski@outlook.com -// */ - package authentication import ( diff --git a/cmd/gogoodwe/powerstation/powerstation.go b/cmd/gogoodwe/powerstation/powerstation.go index 36d10cf..ef4c33e 100644 --- a/cmd/gogoodwe/powerstation/powerstation.go +++ b/cmd/gogoodwe/powerstation/powerstation.go @@ -12,6 +12,7 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" "github.com/logrusorgru/aurora" + "github.com/valyala/fastjson" ) // fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API @@ -48,14 +49,21 @@ func FetchData(Account string, Password string, PowerStationID string) error { } // Get output - dataOutput, jsonerr := utils.GetDataJSON(&powerstationData) - if jsonerr != nil { + dataOutput, err := utils.GetDataJSON(&powerstationData) + if err != nil { utils.HandleError(errors.New("error: converting powerstation data")) - return jsonerr + return err + } + + //parse JSON output + var parser fastjson.Parser + output, err := parser.Parse(string(dataOutput)) + if err != nil { + utils.HandleError(errors.New("error: parsing powerstation data")) + return err } - //Display output - fmt.Println(aurora.BrightYellow(string(dataOutput))) + fmt.Println(aurora.BrightYellow(output)) return nil } diff --git a/go.mod b/go.mod index b7f56b9..736a067 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 require ( github.com/alexflint/go-arg v1.4.3 github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/valyala/fastjson v1.6.4 ) require github.com/alexflint/go-scalar v1.2.0 // indirect diff --git a/go.sum b/go.sum index 899e74f..cc942b3 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= +github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 47eaeb60dc73a1ca1632e22346cff9b7adf62bf6 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Mon, 12 Feb 2024 15:22:48 +1100 Subject: [PATCH 22/26] major code cleanup and reduced complexity --- cmd/gogoodwe/powerstation/constants.go | 9 +++ .../{powerstation.go => fetch.go} | 24 ++---- cmd/gogoodwe/powerstation/inverter.go | 75 ------------------- cmd/gogoodwe/powerstation/monitordetails.go | 64 ++++++++++++++++ cmd/gogoodwe/powerstation/utils.go | 51 +++++++++++++ cmd/gogoodwe/semsapi/apilogin.go | 23 ------ cmd/gogoodwe/semsapi/constants.go | 6 ++ .../authentication.go => semsapi/login.go} | 43 +++++------ .../authutils.go => semsapi/utils.go} | 23 +++--- .../{inverterrdata.go => inverterdata.go} | 0 cmd/gogoodwe/types/logindata.go | 10 --- cmd/gogoodwe/utils/headerutils.go | 15 ---- cmd/gogoodwe/utils/jsonutils.go | 36 --------- 13 files changed, 165 insertions(+), 214 deletions(-) create mode 100644 cmd/gogoodwe/powerstation/constants.go rename cmd/gogoodwe/powerstation/{powerstation.go => fetch.go} (62%) delete mode 100644 cmd/gogoodwe/powerstation/inverter.go create mode 100644 cmd/gogoodwe/powerstation/monitordetails.go create mode 100644 cmd/gogoodwe/powerstation/utils.go delete mode 100644 cmd/gogoodwe/semsapi/apilogin.go create mode 100644 cmd/gogoodwe/semsapi/constants.go rename cmd/gogoodwe/{authentication/authentication.go => semsapi/login.go} (52%) rename cmd/gogoodwe/{authentication/authutils.go => semsapi/utils.go} (87%) rename cmd/gogoodwe/types/{inverterrdata.go => inverterdata.go} (100%) delete mode 100644 cmd/gogoodwe/types/logindata.go delete mode 100644 cmd/gogoodwe/utils/headerutils.go diff --git a/cmd/gogoodwe/powerstation/constants.go b/cmd/gogoodwe/powerstation/constants.go new file mode 100644 index 0000000..9976090 --- /dev/null +++ b/cmd/gogoodwe/powerstation/constants.go @@ -0,0 +1,9 @@ +package powerstation + +const ( + // Powerstation API Url + PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" + + // Default timeout value + HTTPTimeout int = 20 +) diff --git a/cmd/gogoodwe/powerstation/powerstation.go b/cmd/gogoodwe/powerstation/fetch.go similarity index 62% rename from cmd/gogoodwe/powerstation/powerstation.go rename to cmd/gogoodwe/powerstation/fetch.go index ef4c33e..053bb62 100644 --- a/cmd/gogoodwe/powerstation/powerstation.go +++ b/cmd/gogoodwe/powerstation/fetch.go @@ -15,7 +15,6 @@ import ( "github.com/valyala/fastjson" ) -// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API func FetchData(Account string, Password string, PowerStationID string) error { // User account struct @@ -25,31 +24,20 @@ func FetchData(Account string, Password string, PowerStationID string) error { PowerStationID: PowerStationID, } - // Create a new LoginDataFlow object reference - loginDataFlow := &types.LoginDataFlow{ - LoginCreds: creds, - LoginResp: &types.LoginResponse{}, - } - // Do the login..check for errors - err := semsapi.ApiLogin(loginDataFlow) + loginApiResponse, err := semsapi.Login(creds) if err != nil { utils.HandleError(err) return err } - // Powerstation Output Data - powerstationData := types.InverterData{} - - // Fetch the data - fetchDataerr := fetchInverterData(loginDataFlow, &powerstationData) - if fetchDataerr != nil { - utils.HandleError(errors.New("error: fetching powerstation data, check powerstationid is correct")) - return fetchDataerr + powerstationData, err := getMonitorDetailByPowerstationId(creds, loginApiResponse) + if err != nil { + utils.HandleError(err) + return err } - // Get output - dataOutput, err := utils.GetDataJSON(&powerstationData) + dataOutput, err := getDataJSON(powerstationData) if err != nil { utils.HandleError(errors.New("error: converting powerstation data")) return err diff --git a/cmd/gogoodwe/powerstation/inverter.go b/cmd/gogoodwe/powerstation/inverter.go deleted file mode 100644 index 7532a8a..0000000 --- a/cmd/gogoodwe/powerstation/inverter.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -# Name: inverter - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package powerstation - -import ( - "bytes" - "net/http" - "time" - - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" -) - -const ( - // Powerstation API Url - PowerStationURL string = "v2/PowerStation/GetMonitorDetailByPowerstationId" - - // Default timeout value - HTTPTimeout int = 20 -) - -// fetchInverterData - Fetches Data from the Inverter via the specified PowerstationID using the SEMs API -func fetchInverterData(UserLoginFlow *types.LoginDataFlow, PowerstationOutputData *types.InverterData) error { - - // get the Token header data - tokenMapJSONData, err := utils.DataTokenJSON(UserLoginFlow.LoginResp) - if err != nil { - return err - } - - // get the Powerstation ID header data - powerStationMapJSONData, err := utils.PowerStationIdJSON(UserLoginFlow.LoginCreds) - if err != nil { - return err - } - - //Get the url from the Auth API and append the data url part - url := (UserLoginFlow.LoginResp.API + PowerStationURL) - - // Create a new http request - req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationMapJSONData)) - if err != nil { - return err - } - - //Add headers pass in the pointer to set the headers on the request object - utils.SetHeaders(req, tokenMapJSONData) - - //make the API Call - client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} - resp, err := client.Do(req) - if err != nil { - return err - } - - //cleanup - defer resp.Body.Close() - - // Get the response body - respBody, err := utils.FetchResponseBody(resp.Body) - if err != nil { - return err - } - - //marshall response to SemsRespInfo struct - dataStructErr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) - if dataStructErr != nil { - return dataStructErr - } - - return nil - -} diff --git a/cmd/gogoodwe/powerstation/monitordetails.go b/cmd/gogoodwe/powerstation/monitordetails.go new file mode 100644 index 0000000..13eb61a --- /dev/null +++ b/cmd/gogoodwe/powerstation/monitordetails.go @@ -0,0 +1,64 @@ +package powerstation + +import ( + "bytes" + "net/http" + "time" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse) (*types.InverterData, error) { + + // Inverter output struct + inverterOutputData := types.InverterData{} + + // get the Token header data + apiResponseJsonData, err := dataTokenJSON(LoginApiResponse) + if err != nil { + return nil, err + } + + // get the Powerstation ID header data + powerStationIdJsonData, err := powerStationIdJSON(LoginCredentials) + if err != nil { + return nil, err + } + + //Get the url from the Auth API and append the data url part + url := (LoginApiResponse.API + PowerStationURL) + + // Create a new http request + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationIdJsonData)) + if err != nil { + return nil, err + } + + //Add headers pass in the pointer to set the headers on the request object + setHeaders(req, apiResponseJsonData) + + //make the API Call + client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + //cleanup + defer resp.Body.Close() + + // Get the response body + respBody, err := utils.FetchResponseBody(resp.Body) + if err != nil { + return nil, err + } + + //marshall response to SemsRespInfo struct + inverterDataerr := utils.UnmarshalDataToStruct(respBody, &inverterOutputData) + if inverterDataerr != nil { + return nil, inverterDataerr + } + + return &inverterOutputData, nil +} diff --git a/cmd/gogoodwe/powerstation/utils.go b/cmd/gogoodwe/powerstation/utils.go new file mode 100644 index 0000000..4afbf8c --- /dev/null +++ b/cmd/gogoodwe/powerstation/utils.go @@ -0,0 +1,51 @@ +/* +# Name: powerstationhelper - helper functions to get the Powerstation Data from the API +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ +package powerstation + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +// setHeaders - Set the headers for the SEMS Data API +func setHeaders(r *http.Request, tokenstring []byte) { + r.Header.Add("Content-Type", "application/json") + r.Header.Add("Token", string(tokenstring)) +} + +// PowerStationIdJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string +func powerStationIdJSON(UserLogin *types.LoginCredentials) ([]byte, error) { + powerStationMap := make(map[string]string) + powerStationMap["powerStationId"] = UserLogin.PowerStationID + + // convert to byte[] + jsonStr, err := json.Marshal(powerStationMap) + return jsonStr, err +} + +func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { + tokenMap := make(map[string]string) + tokenMap["version"] = "v2.1.0" + tokenMap["client"] = "ios" + tokenMap["language"] = "en" + tokenMap["timestamp"] = strconv.FormatInt(SemsResponseData.Data.Timestamp, 10) + tokenMap["uid"] = SemsResponseData.Data.UID + tokenMap["token"] = SemsResponseData.Data.Token + + // convert to byte[] + jsonStr, err := json.Marshal(tokenMap) + return jsonStr, err +} + +func getDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { + + // Get the response and return any errors + resp, err := utils.MarshalStructToJSON(&PowerstationOutputData) + return resp, err +} diff --git a/cmd/gogoodwe/semsapi/apilogin.go b/cmd/gogoodwe/semsapi/apilogin.go deleted file mode 100644 index 56990e2..0000000 --- a/cmd/gogoodwe/semsapi/apilogin.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -# Name: apiLogin - Logs in to the SEMS API -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package semsapi - -import ( - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/authentication" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" -) - -// ApiLogin - Login to the SEMS API -func ApiLogin(UserLoginFlow *types.LoginDataFlow) error { - - // Do the login - update the pointer to the struct SemsResponseData - err := authentication.DoLogin(UserLoginFlow) - if err != nil { - utils.HandleError(err) - } - - return err -} diff --git a/cmd/gogoodwe/semsapi/constants.go b/cmd/gogoodwe/semsapi/constants.go new file mode 100644 index 0000000..4f618b5 --- /dev/null +++ b/cmd/gogoodwe/semsapi/constants.go @@ -0,0 +1,6 @@ +package semsapi + +const ( + AuthLoginURL = "https://www.semsportal.com/api/v2/Common/CrossLogin" + HTTPTimeout = 20 // seconds +) diff --git a/cmd/gogoodwe/authentication/authentication.go b/cmd/gogoodwe/semsapi/login.go similarity index 52% rename from cmd/gogoodwe/authentication/authentication.go rename to cmd/gogoodwe/semsapi/login.go index a8e106e..8d9fb9b 100644 --- a/cmd/gogoodwe/authentication/authentication.go +++ b/cmd/gogoodwe/semsapi/login.go @@ -1,4 +1,4 @@ -package authentication +package semsapi import ( "bytes" @@ -9,29 +9,27 @@ import ( "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -const ( - AuthLoginURL = "https://www.semsportal.com/api/v2/Common/CrossLogin" - HTTPTimeout = 20 // seconds -) +// Login - Login to the SEMS API passing in a LoginCredentials struct and returning a LoginResponse struct. +func Login(LoginCredentials *types.LoginCredentials) (*types.LoginResponse, error) { -// Login - Login to the SEMS API -func DoLogin(UserLoginFlow *types.LoginDataFlow) error { + // API Response struct + loginApiResponse := types.LoginResponse{} //check if the UserLogin struct is empty - if usererr := checkUserLoginInfo(UserLoginFlow.LoginCreds); usererr != nil { - return usererr + if err := checkUserLoginInfo(LoginCredentials); err != nil { + return nil, err } // User login struct to be converted to JSON - jsonData, jsonErr := utils.MarshalStructToJSON(UserLoginFlow.LoginCreds) - if jsonErr != nil { - return jsonErr + loginData, err := utils.MarshalStructToJSON(LoginCredentials) + if err != nil { + return nil, err } // Create a new http request - req, err := http.NewRequest(http.MethodPost, AuthLoginURL, bytes.NewBuffer(jsonData)) + req, err := http.NewRequest(http.MethodPost, AuthLoginURL, bytes.NewBuffer(loginData)) if err != nil { - return err + return nil, err } //Add headers pass in the pointer to set the headers on the request object @@ -41,29 +39,28 @@ func DoLogin(UserLoginFlow *types.LoginDataFlow) error { client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} resp, err := client.Do(req) if err != nil { - return err + return nil, err } - //cleanup defer resp.Body.Close() // Get the response body respBody, respErr := utils.FetchResponseBody(resp.Body) if respErr != nil { - return respErr + return nil, respErr } - //marshall response to SemsRespInfo struct - dataErr := utils.UnmarshalDataToStruct(respBody, &UserLoginFlow.LoginResp) + //marshall response to loginresponse struct + dataErr := utils.UnmarshalDataToStruct(respBody, &loginApiResponse) if dataErr != nil { - return dataErr + return nil, dataErr } // check for successful login return value..return a login error - loginErr := checkUserLoginResponse(UserLoginFlow.LoginResp.Msg) + loginErr := checkUserLoginResponse(loginApiResponse.Msg) if loginErr != nil { - return loginErr + return nil, loginErr } - return nil + return &loginApiResponse, nil } diff --git a/cmd/gogoodwe/authentication/authutils.go b/cmd/gogoodwe/semsapi/utils.go similarity index 87% rename from cmd/gogoodwe/authentication/authutils.go rename to cmd/gogoodwe/semsapi/utils.go index 8a37c9b..17ac244 100644 --- a/cmd/gogoodwe/authentication/authutils.go +++ b/cmd/gogoodwe/semsapi/utils.go @@ -1,9 +1,4 @@ -/* -# Name: authentication - auth helper functions -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ - -package authentication +package semsapi import ( "errors" @@ -19,14 +14,6 @@ func setHeaders(r *http.Request) { r.Header.Add("Token", "{\"version\":\"v2.1.0\",\"client\":\"ios\",\"language\":\"en\"}") } -// CheckUserLoginInfo - Check user login struct is valid/not null -func checkUserLoginInfo(UserLogin *types.LoginCredentials) error { - if *UserLogin == (types.LoginCredentials{}) { - return errors.New("**Error: User Login details are empty or invalid..**") - } - return nil -} - // CheckUserLoginResponse - check for successful login return value..return a login error func checkUserLoginResponse(loginResponse string) error { if strings.Compare(loginResponse, "Successful") != 0 { @@ -34,3 +21,11 @@ func checkUserLoginResponse(loginResponse string) error { } return nil } + +// CheckUserLoginInfo - Check user login struct is valid/not null +func checkUserLoginInfo(UserLogin *types.LoginCredentials) error { + if *UserLogin == (types.LoginCredentials{}) { + return errors.New("**Error: User Login details are empty or invalid..**") + } + return nil +} diff --git a/cmd/gogoodwe/types/inverterrdata.go b/cmd/gogoodwe/types/inverterdata.go similarity index 100% rename from cmd/gogoodwe/types/inverterrdata.go rename to cmd/gogoodwe/types/inverterdata.go diff --git a/cmd/gogoodwe/types/logindata.go b/cmd/gogoodwe/types/logindata.go deleted file mode 100644 index 2be2a26..0000000 --- a/cmd/gogoodwe/types/logindata.go +++ /dev/null @@ -1,10 +0,0 @@ -package types - -/* -# Name: LoginDataFlow - Struct to hold pointers to User login data structs -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -type LoginDataFlow struct { - LoginCreds *LoginCredentials - LoginResp *LoginResponse -} diff --git a/cmd/gogoodwe/utils/headerutils.go b/cmd/gogoodwe/utils/headerutils.go deleted file mode 100644 index e96fa9b..0000000 --- a/cmd/gogoodwe/utils/headerutils.go +++ /dev/null @@ -1,15 +0,0 @@ -/* -# Name: powerstationhelper - helper functions to get the Powerstation Data from the API -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package utils - -import ( - "net/http" -) - -// setHeaders - Set the headers for the SEMS Data API -func SetHeaders(r *http.Request, tokenstring []byte) { - r.Header.Add("Content-Type", "application/json") - r.Header.Add("Token", string(tokenstring)) -} diff --git a/cmd/gogoodwe/utils/jsonutils.go b/cmd/gogoodwe/utils/jsonutils.go index f95c2de..c086406 100644 --- a/cmd/gogoodwe/utils/jsonutils.go +++ b/cmd/gogoodwe/utils/jsonutils.go @@ -6,44 +6,8 @@ package utils import ( "encoding/json" - "strconv" - - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" ) -// DataTokenJSON - Makes a map for the token to be passed to the Data API header and returns a JSON string -func DataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { - tokenMap := make(map[string]string) - tokenMap["version"] = "v2.1.0" - tokenMap["client"] = "ios" - tokenMap["language"] = "en" - tokenMap["timestamp"] = strconv.FormatInt(SemsResponseData.Data.Timestamp, 10) - tokenMap["uid"] = SemsResponseData.Data.UID - tokenMap["token"] = SemsResponseData.Data.Token - - // convert to byte[] - jsonStr, err := json.Marshal(tokenMap) - return jsonStr, err -} - -// PowerStationIdJSON - Makes a map for the powerStationId to be passed to the Data API header and returns a JSON string -func PowerStationIdJSON(UserLogin *types.LoginCredentials) ([]byte, error) { - powerStationMap := make(map[string]string) - powerStationMap["powerStationId"] = UserLogin.PowerStationID - - // convert to byte[] - jsonStr, err := json.Marshal(powerStationMap) - return jsonStr, err -} - -// GetDataJSON - Returns the PowerstationOutputData as JSON -func GetDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { - - // Get the response and return any errors - resp, err := MarshalStructToJSON(&PowerstationOutputData) - return resp, err -} - // UnmarshalDataToStruct - Unmarshall http response to target struct func UnmarshalDataToStruct(respBody []byte, targetStruct interface{}) error { resperr := json.Unmarshal(respBody, &targetStruct) From e80d13db67512a56e1ed4053d9f162dac68d5863 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Wed, 14 Feb 2024 16:04:01 +1100 Subject: [PATCH 23/26] added summary output data via an interface --- README.md | 6 +- cmd/gogoodwe/app/app.go | 3 +- cmd/gogoodwe/interfaces/interfaces.go | 10 +++ cmd/gogoodwe/powerstation/fetch.go | 32 ++-------- cmd/gogoodwe/powerstation/monitordetails.go | 71 +++++++++++++++++---- cmd/gogoodwe/powerstation/utils.go | 8 ++- cmd/gogoodwe/types/dailysummarydata.go | 26 ++++++++ cmd/gogoodwe/utils/args.go | 1 + 8 files changed, 114 insertions(+), 43 deletions(-) create mode 100644 cmd/gogoodwe/interfaces/interfaces.go create mode 100644 cmd/gogoodwe/types/dailysummarydata.go diff --git a/README.md b/README.md index 73853da..3ecb633 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,12 @@ From the command line the usage is pretty simple: ```bash ##Note the use of single quotes '' -./gogoodwe --account '' --pwd '' --powerstationid '' +./gogoodwe --account '' --pwd '' --powerstationid '' --dailysummary # Or -./gogoodwe -a '' -p '' -i '' +./gogoodwe -a '' -p '' -i '' -d + +##where daily summary provides a shorter daily view of the inverter data ``` To get the help on using the command line tool, type: diff --git a/cmd/gogoodwe/app/app.go b/cmd/gogoodwe/app/app.go index dbadd0b..0117dc6 100644 --- a/cmd/gogoodwe/app/app.go +++ b/cmd/gogoodwe/app/app.go @@ -25,5 +25,6 @@ func Run() error { } // Get the data from the API, return any errors. Pass in args as string - return powerstation.FetchData(args.Account, args.Password, args.PowerStationID) + return powerstation.FetchData(args.Account, args.Password, args.PowerStationID, args.DailySummary) + } diff --git a/cmd/gogoodwe/interfaces/interfaces.go b/cmd/gogoodwe/interfaces/interfaces.go new file mode 100644 index 0000000..7e705cb --- /dev/null +++ b/cmd/gogoodwe/interfaces/interfaces.go @@ -0,0 +1,10 @@ +package interfaces + +import ( + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" +) + +// Constraints for functions that return data from the API via marshalled structs +type ISemsDataConstraint interface { + types.InverterData | types.DailySummaryData +} diff --git a/cmd/gogoodwe/powerstation/fetch.go b/cmd/gogoodwe/powerstation/fetch.go index 053bb62..4513b4f 100644 --- a/cmd/gogoodwe/powerstation/fetch.go +++ b/cmd/gogoodwe/powerstation/fetch.go @@ -5,17 +5,12 @@ package powerstation import ( - "errors" - "fmt" - "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/semsapi" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" - "github.com/logrusorgru/aurora" - "github.com/valyala/fastjson" ) -func FetchData(Account string, Password string, PowerStationID string) error { +func FetchData(Account string, Password string, PowerStationID string, DailySummary bool) error { // User account struct creds := &types.LoginCredentials{ @@ -31,27 +26,14 @@ func FetchData(Account string, Password string, PowerStationID string) error { return err } - powerstationData, err := getMonitorDetailByPowerstationId(creds, loginApiResponse) - if err != nil { - utils.HandleError(err) - return err - } - - dataOutput, err := getDataJSON(powerstationData) - if err != nil { - utils.HandleError(errors.New("error: converting powerstation data")) - return err - } + //fetch data based on + if DailySummary { + getMonitorSummaryByPowerstationId(creds, loginApiResponse) - //parse JSON output - var parser fastjson.Parser - output, err := parser.Parse(string(dataOutput)) - if err != nil { - utils.HandleError(errors.New("error: parsing powerstation data")) - return err + } else { + //powerstationData = types.InverterData + getMonitorDetailByPowerstationId(creds, loginApiResponse) } - fmt.Println(aurora.BrightYellow(output)) - return nil } diff --git a/cmd/gogoodwe/powerstation/monitordetails.go b/cmd/gogoodwe/powerstation/monitordetails.go index 13eb61a..2cc99d9 100644 --- a/cmd/gogoodwe/powerstation/monitordetails.go +++ b/cmd/gogoodwe/powerstation/monitordetails.go @@ -2,28 +2,28 @@ package powerstation import ( "bytes" + "errors" "net/http" "time" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/interfaces" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) -func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse) (*types.InverterData, error) { - - // Inverter output struct - inverterOutputData := types.InverterData{} +// Generic function to retrieve data from the API via an ISemsDataConstraint Interface of defined structs +func getMonitorData[T interfaces.ISemsDataConstraint](LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse, InverterOutput *T) error { // get the Token header data apiResponseJsonData, err := dataTokenJSON(LoginApiResponse) if err != nil { - return nil, err + return err } // get the Powerstation ID header data powerStationIdJsonData, err := powerStationIdJSON(LoginCredentials) if err != nil { - return nil, err + return err } //Get the url from the Auth API and append the data url part @@ -32,7 +32,7 @@ func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, // Create a new http request req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(powerStationIdJsonData)) if err != nil { - return nil, err + return err } //Add headers pass in the pointer to set the headers on the request object @@ -42,7 +42,7 @@ func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, client := &http.Client{Timeout: time.Duration(HTTPTimeout) * time.Second} resp, err := client.Do(req) if err != nil { - return nil, err + return err } //cleanup @@ -51,14 +51,59 @@ func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, // Get the response body respBody, err := utils.FetchResponseBody(resp.Body) if err != nil { - return nil, err + return err } - //marshall response to SemsRespInfo struct - inverterDataerr := utils.UnmarshalDataToStruct(respBody, &inverterOutputData) + //marshall response to struct pointer + inverterDataerr := utils.UnmarshalDataToStruct(respBody, &InverterOutput) if inverterDataerr != nil { - return nil, inverterDataerr + return inverterDataerr + } + + return nil + +} + +// Get Monitor Detailed data +func getMonitorDetailByPowerstationId(LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse) { + var powerstationData types.InverterData + + err := getMonitorData(LoginCredentials, LoginApiResponse, &powerstationData) + if err != nil { + utils.HandleError(err) + } + + dataOutput, err := getDataJSON(powerstationData) + if err != nil { + utils.HandleError(errors.New("error: converting powerstation data")) + } + + output, err := parseOutput(dataOutput) + if err != nil { + utils.HandleError(err) + } + printOutput(output) + +} + +// Get Monitor sumary data +func getMonitorSummaryByPowerstationId(LoginCredentials *types.LoginCredentials, LoginApiResponse *types.LoginResponse) { + + var powerstationData types.DailySummaryData + err := getMonitorData(LoginCredentials, LoginApiResponse, &powerstationData) + if err != nil { + utils.HandleError(err) + } + + dataOutput, err := getDataJSON(powerstationData) + if err != nil { + utils.HandleError(errors.New("error: converting powerstation summary data")) + } + + output, err := parseOutput(dataOutput) + if err != nil { + utils.HandleError(err) } + printOutput(output) - return &inverterOutputData, nil } diff --git a/cmd/gogoodwe/powerstation/utils.go b/cmd/gogoodwe/powerstation/utils.go index 4afbf8c..22537a4 100644 --- a/cmd/gogoodwe/powerstation/utils.go +++ b/cmd/gogoodwe/powerstation/utils.go @@ -9,6 +9,7 @@ import ( "net/http" "strconv" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/interfaces" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" ) @@ -43,9 +44,12 @@ func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { return jsonStr, err } -func getDataJSON(PowerstationOutputData *types.InverterData) ([]byte, error) { + + +// parse json data +func getDataJSON[T interfaces.ISemsDataConstraint](data T) ([]byte, error) { // Get the response and return any errors - resp, err := utils.MarshalStructToJSON(&PowerstationOutputData) + resp, err := utils.MarshalStructToJSON(&data) return resp, err } diff --git a/cmd/gogoodwe/types/dailysummarydata.go b/cmd/gogoodwe/types/dailysummarydata.go new file mode 100644 index 0000000..b6c0c03 --- /dev/null +++ b/cmd/gogoodwe/types/dailysummarydata.go @@ -0,0 +1,26 @@ +/* +# Name: DailySummaryData - Struct to hold daily summary data +*/ +package types + +type DailySummaryData struct { + Language string `json:"language"` + Function []string `json:"function"` + 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"` +} diff --git a/cmd/gogoodwe/utils/args.go b/cmd/gogoodwe/utils/args.go index ff9a304..9292c71 100644 --- a/cmd/gogoodwe/utils/args.go +++ b/cmd/gogoodwe/utils/args.go @@ -10,6 +10,7 @@ type Args struct { Account string `arg:"required,-a,--account" help:"SEMS Email Account."` Password string `arg:"required,-p,--password" help:"SEMS Account password."` PowerStationID string `arg:"required,-i,--powerstationid" help:"SEMS Powerstation ID."` + DailySummary bool `arg:"-d,--dailysummary" help:"Output as a daily summary."` } // Description - App description From 5a229170809d3f7ab13dc371ffef2a1f48e5757f Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Wed, 14 Feb 2024 16:06:19 +1100 Subject: [PATCH 24/26] updated readme --- TODO.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index ef01c7d..ab38e91 100644 --- a/TODO.md +++ b/TODO.md @@ -7,9 +7,7 @@ - [ ] Add the ability to query historical data for a single day. - [ ] Have the ability to have a realtime logging to the screen or to a file in 5 minute intervals. - [ ] Add the ability to produce a daily summary of key data (Generation today, Income today, total generation, total income). -- [ ] Add the ability to query the inverter status for Generation today and Status (check if operational). - [ ] Add goroutines and wait groups for the API calls and maybe channels for success/failed API calls. -- [ ] Add Cobra for command flag parsing and processing. - [ ] Investigate the ability to generate .CSV files as output. - [ ] @@ -20,3 +18,4 @@ ### Done ✓ - [ ] +- [x] Add the ability to query the inverter status for Generation today and Status (check if operational). From 481b0aa2368598a6e306ab92dc677444d34eeecc Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Wed, 14 Feb 2024 16:07:43 +1100 Subject: [PATCH 25/26] updated readme --- TODO.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index ab38e91..9ffce79 100644 --- a/TODO.md +++ b/TODO.md @@ -2,13 +2,6 @@ ### ToDo -- [ ] Add ability to output inverter data to a file. -- [ ] Format the inverter output to make it more human readable. -- [ ] Add the ability to query historical data for a single day. -- [ ] Have the ability to have a realtime logging to the screen or to a file in 5 minute intervals. -- [ ] Add the ability to produce a daily summary of key data (Generation today, Income today, total generation, total income). -- [ ] Add goroutines and wait groups for the API calls and maybe channels for success/failed API calls. -- [ ] Investigate the ability to generate .CSV files as output. - [ ] ### In Progress @@ -17,5 +10,14 @@ ### Done ✓ -- [ ] - [x] Add the ability to query the inverter status for Generation today and Status (check if operational). + +### Future/Roadmap + +- [ ] Format the inverter output to make it more human readable. +- [ ] Add ability to output inverter data to a file. +- [ ] Add the ability to query historical data for a single day. +- [ ] Have the ability to have a realtime logging to the screen or to a file in 5 minute intervals. +- [ ] Add the ability to produce a daily summary of key data (Generation today, Income today, total generation, total income). +- [ ] Add goroutines and wait groups for the API calls and maybe channels for success/failed API calls. +- [ ] Investigate the ability to generate .CSV files as output. From a2068c521bbd4c32a8f502e1e5f8d5a96ec35ef3 Mon Sep 17 00:00:00 2001 From: aaronsaikovski Date: Wed, 14 Feb 2024 16:10:25 +1100 Subject: [PATCH 26/26] updated summary struct --- cmd/gogoodwe/powerstation/utils.go | 2 -- cmd/gogoodwe/types/dailysummarydata.go | 9 ++++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd/gogoodwe/powerstation/utils.go b/cmd/gogoodwe/powerstation/utils.go index 22537a4..5aa7c76 100644 --- a/cmd/gogoodwe/powerstation/utils.go +++ b/cmd/gogoodwe/powerstation/utils.go @@ -44,8 +44,6 @@ func dataTokenJSON(SemsResponseData *types.LoginResponse) ([]byte, error) { return jsonStr, err } - - // parse json data func getDataJSON[T interfaces.ISemsDataConstraint](data T) ([]byte, error) { diff --git a/cmd/gogoodwe/types/dailysummarydata.go b/cmd/gogoodwe/types/dailysummarydata.go index b6c0c03..dd58b16 100644 --- a/cmd/gogoodwe/types/dailysummarydata.go +++ b/cmd/gogoodwe/types/dailysummarydata.go @@ -4,11 +4,10 @@ package types type DailySummaryData struct { - Language string `json:"language"` - Function []string `json:"function"` - HasError bool `json:"hasError"` - Msg string `json:"msg"` - Code string `json:"code"` + 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"`