diff --git a/.DS_Store b/.DS_Store index fc263ad..a883a69 100644 Binary files a/.DS_Store and b/.DS_Store differ 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/.gitignore b/.gitignore index 0b59c55..e9d44f5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +*.DS_Store # Test binary, built with `go test -c` *.test @@ -28,3 +29,5 @@ bin/ output*.* out*.* out.txt + +.idea/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a65edfe..ce9a1a1 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -12,7 +12,8 @@ builds: goos: - linux - windows - - darwin + - darwin + main: ./cmd/gogoodwe archives: - format: tar.gz diff --git a/CHANGELOG.md b/CHANGELOG.md index c3d8e47..a47cd84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,22 @@ -# CHANGELOG +# GoGoodwe - CHANGELOG + +## v2.0.0 (2024-01-22) + +- Major refactoring to cleanup project structure. +- Simplified package structure. +- refactored structs to use pointers more efficiently. + +## v1.4.0 (2023-08-30) + +- Major refactoring to move non-shared code to /internal folder +- Abstracted core away from main() + +## v1.1.0 (2023-08-16) + +- refactored code to make errors bubble back to main package and better error reporting/logging +- refactored main package to include run() method +- removed staticcheck as there is a bug with Go v1.21 ## v1.0.0 (2023-08-10) + - initial version 1.0 release diff --git a/LICENSE b/LICENSE index 8480972..06c0e27 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023, Aaron Saikovski +Copyright (c) 2024, Aaron Saikovski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index d82dc9a..5e177ad 100644 --- a/Makefile +++ b/Makefile @@ -1,68 +1,92 @@ # Define Go command and flags GO = go GOFLAGS = -ldflags="-s -w" - -#export PATH=$PATH:$HOME/go/bin; - -# Define the target executable TARGET = gogoodwe +MAINAPPPATH = ./cmd/${TARGET}/main.go +default: help + +.PHONY: help ## help - Display help about make targets for this Makefile help: @cat Makefile | grep '^## ' --color=never | cut -c4- | sed -e "`printf 's/ - /\t- /;'`" | column -s "`printf '\t'`" -t -## localrelease - Builds the project in preparation for (local)release -localrelease: - go build $(GOFLAGS) -o bin/${TARGET} main.go + +.PHONY: release +## release - Builds the project in preparation for (local)release +release: vet lint seccheck + go build $(GOFLAGS) -o bin/${TARGET} ${MAINAPPPATH} file bin/${TARGET} -## release - Builds the project in preparation for release -release: - goreleaser release --snapshot --clean - -## debug - Builds the project in preparation for debug + +.PHONY: goreleaser +## goreleaser - Builds the project in preparation for release +goreleaser: + goreleaser release --snapshot --clean + + +.PHONY: docs +## docs - updates the swagger docs +docs: + swag init + + +.PHONY: build +## build - Builds the project in preparation for debug build: - go build -o bin/${TARGET} main.go + go build -o bin/${TARGET} ${MAINAPPPATH} file bin/${TARGET} -## buildandrun - builds and runs the program on the target platform -buildandrun: build - ./bin/${TARGET} -## run - runs main.go for testing -run: dep - go run main.go +.PHONY: run +## run - builds and runs the program on the target platform +run: + go run ${MAINAPPPATH} +.PHONY: clean ## clean - Remove the old builds and any debug information clean: + go clean -cache go clean rm -rf dist rm bin/${TARGET} -## test - executes unit test + +.PHONY: test +## test - executes unit tests test: - go test ./... + go test -v ./test/... -## dep - fetches any external dependencies -dep: + +.PHONY: deps +## deps - fetches any external dependencies and updates +deps: go mod tidy go mod download + go get -u ./... + +.PHONY: vet ## vet - Vet examines Go source code and reports suspicious constructs vet: go vet ./... -## staticcheck - Runs static code analyzer staticcheck -staticcheck: - go run honnef.co/go/tools/cmd/staticcheck@latest -checks=all,-ST1000,-U1000 ./... +.PHONY: staticcheck +## staticcheck - Runs static code analyzer staticcheck - currently broken +staticcheck: + staticcheck ./... + + +.PHONY: seccheck ## seccheck - Code vulnerability check seccheck: - brew install govulncheck - govulncheck ./... + #govulncheck ./... + +.PHONY: lint ## lint - format code and tidy modules lint: go fmt ./... - go mod tidy -v \ No newline at end of file + go mod tidy -v diff --git a/README.md b/README.md index 491243a..132e902 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@
+# GoGoodwe V2.0 +======= -# GoGoodwe - -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, @@ -46,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/TODO.md b/TODO.md index 3143314..9ffce79 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,23 @@ -# 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 +- [ ] ### In Progress +- [ ] add unit tests + ### 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. diff --git a/cmd/gogoodwe/app/app.go b/cmd/gogoodwe/app/app.go new file mode 100644 index 0000000..0117dc6 --- /dev/null +++ b/cmd/gogoodwe/app/app.go @@ -0,0 +1,30 @@ +package app + +// Main package - This is the main program entry point +import ( + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/powerstation" + "github.com/AaronSaikovski/gogoodwe/cmd/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 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/main.go b/cmd/gogoodwe/main.go new file mode 100644 index 0000000..a418908 --- /dev/null +++ b/cmd/gogoodwe/main.go @@ -0,0 +1,19 @@ +/* +Package main implements a program that authenticates to and queries the SEMS Solar inverter API. +*/ +package main + +import ( + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/app" + "log" +) + +func main() { + if err := runApp(); err != nil { + log.Fatalf("error: %v", err) + } +} + +func runApp() error { + return app.Run() +} 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/fetch.go b/cmd/gogoodwe/powerstation/fetch.go new file mode 100644 index 0000000..4513b4f --- /dev/null +++ b/cmd/gogoodwe/powerstation/fetch.go @@ -0,0 +1,39 @@ +/* +# 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 ( + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/semsapi" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +func FetchData(Account string, Password string, PowerStationID string, DailySummary bool) error { + + // User account struct + creds := &types.LoginCredentials{ + Account: Account, + Password: Password, + PowerStationID: PowerStationID, + } + + // Do the login..check for errors + loginApiResponse, err := semsapi.Login(creds) + if err != nil { + utils.HandleError(err) + return err + } + + //fetch data based on + if DailySummary { + getMonitorSummaryByPowerstationId(creds, loginApiResponse) + + } else { + //powerstationData = types.InverterData + getMonitorDetailByPowerstationId(creds, loginApiResponse) + } + + return nil +} diff --git a/cmd/gogoodwe/powerstation/monitordetails.go b/cmd/gogoodwe/powerstation/monitordetails.go new file mode 100644 index 0000000..2cc99d9 --- /dev/null +++ b/cmd/gogoodwe/powerstation/monitordetails.go @@ -0,0 +1,109 @@ +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" +) + +// 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 err + } + + // get the Powerstation ID header data + powerStationIdJsonData, err := powerStationIdJSON(LoginCredentials) + if err != nil { + return 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 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 err + } + + //cleanup + defer resp.Body.Close() + + // Get the response body + respBody, err := utils.FetchResponseBody(resp.Body) + if err != nil { + return err + } + + //marshall response to struct pointer + inverterDataerr := utils.UnmarshalDataToStruct(respBody, &InverterOutput) + if inverterDataerr != nil { + 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) + +} diff --git a/cmd/gogoodwe/powerstation/utils.go b/cmd/gogoodwe/powerstation/utils.go new file mode 100644 index 0000000..5aa7c76 --- /dev/null +++ b/cmd/gogoodwe/powerstation/utils.go @@ -0,0 +1,53 @@ +/* +# 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/interfaces" + "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 +} + +// parse json data +func getDataJSON[T interfaces.ISemsDataConstraint](data T) ([]byte, error) { + + // Get the response and return any errors + resp, err := utils.MarshalStructToJSON(&data) + return resp, 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/semsapi/login.go b/cmd/gogoodwe/semsapi/login.go new file mode 100644 index 0000000..8d9fb9b --- /dev/null +++ b/cmd/gogoodwe/semsapi/login.go @@ -0,0 +1,66 @@ +package semsapi + +import ( + "bytes" + "net/http" + "time" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/utils" +) + +// Login - Login to the SEMS API passing in a LoginCredentials struct and returning a LoginResponse struct. +func Login(LoginCredentials *types.LoginCredentials) (*types.LoginResponse, error) { + + // API Response struct + loginApiResponse := types.LoginResponse{} + + //check if the UserLogin struct is empty + if err := checkUserLoginInfo(LoginCredentials); err != nil { + return nil, err + } + + // User login struct to be converted to JSON + 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(loginData)) + if err != nil { + return nil, 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 nil, err + } + + defer resp.Body.Close() + + // Get the response body + respBody, respErr := utils.FetchResponseBody(resp.Body) + if respErr != nil { + return nil, respErr + } + + //marshall response to loginresponse struct + dataErr := utils.UnmarshalDataToStruct(respBody, &loginApiResponse) + if dataErr != nil { + return nil, dataErr + } + + // check for successful login return value..return a login error + loginErr := checkUserLoginResponse(loginApiResponse.Msg) + if loginErr != nil { + return nil, loginErr + } + + return &loginApiResponse, nil +} diff --git a/cmd/gogoodwe/semsapi/utils.go b/cmd/gogoodwe/semsapi/utils.go new file mode 100644 index 0000000..17ac244 --- /dev/null +++ b/cmd/gogoodwe/semsapi/utils.go @@ -0,0 +1,31 @@ +package semsapi + +import ( + "errors" + "net/http" + "strings" + + "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" +) + +// 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\"}") +} + +// 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) + } + 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/dailysummarydata.go b/cmd/gogoodwe/types/dailysummarydata.go new file mode 100644 index 0000000..dd58b16 --- /dev/null +++ b/cmd/gogoodwe/types/dailysummarydata.go @@ -0,0 +1,25 @@ +/* +# Name: DailySummaryData - Struct to hold daily summary data +*/ +package types + +type DailySummaryData struct { + Language string `json:"language"` + HasError bool `json:"hasError"` + Msg string `json:"msg"` + Code string `json:"code"` + Data struct { + Kpi struct { + MonthGeneration float64 `json:"month_generation"` + Power float64 `json:"power"` + TotalPower float64 `json:"total_power"` + DayIncome float64 `json:"day_income"` + TotalIncome float64 `json:"total_income"` + Currency string `json:"currency"` + } `json:"kpi"` + Inverter []struct { + TotalGeneration string `json:"total_generation"` + DailyGeneration string `json:"daily_generation"` + } `json:"inverter"` + } `json:"data"` +} diff --git a/types/stationresponsedata.go b/cmd/gogoodwe/types/inverterdata.go similarity index 72% rename from types/stationresponsedata.go rename to cmd/gogoodwe/types/inverterdata.go index 8239905..81cdbfb 100644 --- a/types/stationresponsedata.go +++ b/cmd/gogoodwe/types/inverterdata.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/cmd/gogoodwe/types/logincredentials.go b/cmd/gogoodwe/types/logincredentials.go new file mode 100644 index 0000000..64a12c1 --- /dev/null +++ b/cmd/gogoodwe/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/cmd/gogoodwe/types/loginresponse.go similarity index 59% rename from types/semsresponsedata.go rename to cmd/gogoodwe/types/loginresponse.go index 5abcd61..5d11885 100644 --- a/types/semsresponsedata.go +++ b/cmd/gogoodwe/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/cmd/gogoodwe/utils/args.go b/cmd/gogoodwe/utils/args.go new file mode 100644 index 0000000..9292c71 --- /dev/null +++ b/cmd/gogoodwe/utils/args.go @@ -0,0 +1,24 @@ +package utils + +var ( + // Version string + VersionString string = "gogoodwe v2.0.0" +) + +// 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."` + DailySummary bool `arg:"-d,--dailysummary" help:"Output as a daily summary."` +} + +// 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 VersionString +} diff --git a/utils/errorhandler.go b/cmd/gogoodwe/utils/errorhandler.go similarity index 99% rename from utils/errorhandler.go rename to cmd/gogoodwe/utils/errorhandler.go index a365476..2beccae 100644 --- a/utils/errorhandler.go +++ b/cmd/gogoodwe/utils/errorhandler.go @@ -2,6 +2,7 @@ package utils import ( "log" + "github.com/logrusorgru/aurora" ) diff --git a/utils/responsehandler.go b/cmd/gogoodwe/utils/jsonutils.go similarity index 70% rename from utils/responsehandler.go rename to cmd/gogoodwe/utils/jsonutils.go index 7f06c10..c086406 100644 --- a/utils/responsehandler.go +++ b/cmd/gogoodwe/utils/jsonutils.go @@ -1,16 +1,13 @@ +/* +# Name: jsonutils - helper functions to get the Powerstation Data from the API +# Author: Aaron Saikovski - asaikovski@outlook.com +*/ 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) 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/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/constants/constants.go b/constants/constants.go deleted file mode 100644 index cb423b5..0000000 --- a/constants/constants.go +++ /dev/null @@ -1,18 +0,0 @@ -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 v0.0.7" - - //API login success response message - SemsLoginSuccessResponse string = "Successful" -) diff --git a/go.mod b/go.mod index c0b1f97..736a067 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/AaronSaikovski/gogoodwe -go 1.21 +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.1.0 // indirect +require github.com/alexflint/go-scalar v1.2.0 // indirect diff --git a/go.sum b/go.sum index d3619e9..cc942b3 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= @@ -13,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= diff --git a/main.go b/main.go deleted file mode 100644 index 62ad0ad..0000000 --- a/main.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -# Name: main package - 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" -) - -// 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) - - //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'.") - } - - // 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/authentication/authentication.go b/pkg/goodwe/authentication/authentication.go deleted file mode 100644 index 5b52eae..0000000 --- a/pkg/goodwe/authentication/authentication.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -# Name: authentication - authenticates to the goodwe API - https://www.semsportal.com/api/v2/Common/CrossLogin -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ - -package authentication - -import ( - "bytes" - "net/http" - "time" - - "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\"}") -} - -// DoLogin - Main public login function -// Logs into the SEMs API -func DoLogin(SemsResponseData *types.SemsResponseData, UserLogin *types.SemsLoginCreds) error { - - //check if the UserLogin struct is empty - usererr := CheckUserLoginInfo(UserLogin) - if usererr != nil { - utils.HandleError(usererr) - return usererr - } - - // User login struct to be converted to JSON - jsonData, _ := utils.MarshalStructToJSON(UserLogin) - - // Create a new http request - 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) - - //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 - } - - //cleanup - defer resp.Body.Close() - - // Get the response body - respBody, _ := utils.FetchResponseBody(resp.Body) - - //marshall response to SemsRespInfo struct - utils.UnmarshalDataToStruct(respBody, &SemsResponseData) - - // check for successful login return value..return a login error - CheckUserLoginResponse(SemsResponseData.Msg) - - return nil - -} diff --git a/pkg/goodwe/authentication/authhelper.go b/pkg/goodwe/authentication/authhelper.go deleted file mode 100644 index b103423..0000000 --- a/pkg/goodwe/authentication/authhelper.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -# Name: authhelper - auth helper functions -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ - -package authentication - -import ( - "errors" - "strings" - - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/types" - "github.com/AaronSaikovski/gogoodwe/utils" -) - -// 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{}) { - return errors.New("**Error: User Login details are empty or invalid..**") - } else { - return nil - } -} - -// CheckUserLoginResponse - check for successful login return value..return a login error -func CheckUserLoginResponse(loginResponse string) { - if strings.Compare(loginResponse, constants.SemsLoginSuccessResponse) != 0 { - authErr := errors.New("API Login Error: " + loginResponse) - utils.HandleError(authErr) - } -} 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/pkg/goodwe/powerstationdata/powerstationdata.go b/pkg/goodwe/powerstationdata/powerstationdata.go deleted file mode 100644 index e4bc859..0000000 --- a/pkg/goodwe/powerstationdata/powerstationdata.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -# Name: powerstationdata - gets data from the goodwe API - "v2/PowerStation/GetMonitorDetailByPowerstationId" -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package powerstationdata - -import ( - "bytes" - "net/http" - "time" - - "github.com/AaronSaikovski/gogoodwe/constants" - "github.com/AaronSaikovski/gogoodwe/types" - "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 { - - // get the Token header data - tokenMapJSONData, _ := DataTokenJSON(SemsResponseData) - - // get the Powerstation ID header data - powerStationMapJSONData, _ := PowerStationIDJSON(UserLogin) - - //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 { - utils.HandleError(err) - } - - //Add headers pass in the pointer to set the headers on the request object - setHeaders(req, tokenMapJSONData) - - //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 - } - - //cleanup - defer resp.Body.Close() - - // Get the response body - respBody, _ := utils.FetchResponseBody(resp.Body) - - //marshall response to SemsRespInfo struct - dataerr := utils.UnmarshalDataToStruct(respBody, &PowerstationOutputData) - if dataerr != nil { - return dataerr - } - - return nil - -} diff --git a/pkg/goodwe/powerstationdata/powerstationdatahelper.go b/pkg/goodwe/powerstationdata/powerstationdatahelper.go deleted file mode 100644 index e6a809b..0000000 --- a/pkg/goodwe/powerstationdata/powerstationdatahelper.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -# Name: powerstationdatahelper - helper functions to get the Powerstation Data from the API -# Author: Aaron Saikovski - asaikovski@outlook.com -*/ -package powerstationdata - -import ( - "encoding/json" - "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) { - - tokenMap := make(map[string]string) - tokenMap["version"] = "v2.1.0" - tokenMap["client"] = "ios" - tokenMap["language"] = "en" - tokenMap["timestamp"] = strconv.Itoa(SemsResponseData.Data.Timestamp) - 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.SemsLoginCreds) ([]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.StationResponseData) ([]byte, error) { - - // Get the response and return any errors - resp, err := utils.MarshalStructToJSON(&PowerstationOutputData) - return resp, 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/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/struct_test.go b/test/struct_test.go new file mode 100644 index 0000000..2add9b8 --- /dev/null +++ b/test/struct_test.go @@ -0,0 +1,21 @@ +/* +A Sample test harness. +*/ + +package testing + +// import ( +// "github.com/AaronSaikovski/gogoodwe/cmd/gogoodwe/types" +// "testing" +// ) + +// // A testing function. +// func TestLoginCredentials(t *testing.T) { + +// expected := "test data" +// ateststruct := types.LoginCredentials{SampleString: "test data", SampleInt: 1} + +// if ateststruct.SampleString != expected { +// t.Errorf("struct expected '%s' but got '%s'", expected, ateststruct.SampleString) +// } +// } 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"` -}