From 84eecff4a13f3d1c5780a9c41fe630ade7c6201f Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 9 Mar 2021 18:05:02 +0100 Subject: [PATCH] Simplify powerwall login --- meter/powerwall/powerwall.go | 31 +++++++++++++++++ meter/tesla.go | 67 ++++++++++-------------------------- 2 files changed, 50 insertions(+), 48 deletions(-) create mode 100644 meter/powerwall/powerwall.go diff --git a/meter/powerwall/powerwall.go b/meter/powerwall/powerwall.go new file mode 100644 index 000000000..ae72a81f4 --- /dev/null +++ b/meter/powerwall/powerwall.go @@ -0,0 +1,31 @@ +package powerwall + +// credits to https://github.com/vloschiavo/powerwall2 + +// URIs +const ( + MeterURI = "/api/meters/aggregates" + BatteryURI = "/api/system_status/soe" + LoginURI = "/api/login/Basic" +) + +// MeterResponse is the /api/system_status/aggregates response +type MeterResponse map[string]struct { + LastCommunicationTime string `json:"last_communication_time"` + InstantPower float64 `json:"instant_power"` + InstantReactivePower float64 `json:"instant_reactive_power"` + InstantApparentPower float64 `json:"instant_apparent_power"` + Frequency float64 `json:"frequency"` + EnergyExported float64 `json:"energy_exported"` + EnergyImported float64 `json:"energy_imported"` + InstantAverageVoltage float64 `json:"instant_average_voltage"` + InstantTotalCurrent float64 `json:"instant_total_current"` + IACurrent float64 `json:"i_a_current"` + IBCurrent float64 `json:"i_b_current"` + ICCurrent float64 `json:"i_c_current"` +} + +// BatteryResponse is the /api/system_status/soe response +type BatteryResponse struct { + Percentage float64 `json:"percentage"` +} diff --git a/meter/tesla.go b/meter/tesla.go index 1fc60fdbf..8ad06e073 100644 --- a/meter/tesla.go +++ b/meter/tesla.go @@ -1,46 +1,21 @@ package meter import ( - "bytes" - "encoding/json" "errors" "fmt" + "net/http" "net/http/cookiejar" "net/url" "strings" "github.com/andig/evcc/api" + "github.com/andig/evcc/meter/powerwall" "github.com/andig/evcc/util" "github.com/andig/evcc/util/request" ) // credits to https://github.com/vloschiavo/powerwall2 -const ( - teslaMeterURI = "/api/meters/aggregates" - teslaBatteryURI = "/api/system_status/soe" - teslaLoginURI = "/api/login/Basic" -) - -type teslaMeterResponse map[string]struct { - LastCommunicationTime string `json:"last_communication_time"` - InstantPower float64 `json:"instant_power"` - InstantReactivePower float64 `json:"instant_reactive_power"` - InstantApparentPower float64 `json:"instant_apparent_power"` - Frequency float64 `json:"frequency"` - EnergyExported float64 `json:"energy_exported"` - EnergyImported float64 `json:"energy_imported"` - InstantAverageVoltage float64 `json:"instant_average_voltage"` - InstantTotalCurrent float64 `json:"instant_total_current"` - IACurrent float64 `json:"i_a_current"` - IBCurrent float64 `json:"i_b_current"` - ICCurrent float64 `json:"i_c_current"` -} - -type teslaBatteryResponse struct { - Percentage float64 `json:"percentage"` -} - // Tesla is the tesla powerwall meter type Tesla struct { *request.Helper @@ -93,7 +68,7 @@ func NewTesla(uri, usage, password string) (api.Meter, error) { m := &Tesla{ Helper: request.NewHelper(log), - uri: util.DefaultScheme(uri, "https"), + uri: util.DefaultScheme(strings.TrimSuffix(uri, "/"), "https"), usage: strings.ToLower(usage), password: password, } @@ -124,30 +99,26 @@ func NewTesla(uri, usage, password string) (api.Meter, error) { // Login calls login and saves the returned cookie func (m *Tesla) Login() error { - // username for the powerwall seems to always be customer; email is not required for authentication - payload := map[string]interface{}{"password": m.password, "username": "customer"} - payloadBytes, err := json.Marshal(payload) - if err != nil { - return err + data := map[string]interface{}{ + "username": "customer", + "password": m.password, } - // returns cookie which is saved in the cookie jar - resp, err := m.Client.Post(m.uri+teslaLoginURI, "application/json", bytes.NewReader(payloadBytes)) - if err != nil { - return err + req, err := request.New(http.MethodPost, m.uri+powerwall.LoginURI, request.MarshalJSON(data), request.JSONEncoding) + if err == nil { + // use DoBody as it will close the response body + if _, err = m.DoBody(req); err != nil { + err = fmt.Errorf("login failed: %w", err) + } } - if body, err := request.ReadBody(resp); err != nil { - return fmt.Errorf("couldn't login: %s: %s", err, string(body)) - } - - return nil + return err } // CurrentPower implements the Meter.CurrentPower interface func (m *Tesla) CurrentPower() (float64, error) { - var res teslaMeterResponse - if err := m.GetJSON(m.uri+teslaMeterURI, &res); err != nil { + var res powerwall.MeterResponse + if err := m.GetJSON(m.uri+powerwall.MeterURI, &res); err != nil { return 0, err } @@ -160,8 +131,8 @@ func (m *Tesla) CurrentPower() (float64, error) { // totalEnergy implements the api.MeterEnergy interface func (m *Tesla) totalEnergy() (float64, error) { - var res teslaMeterResponse - if err := m.GetJSON(m.uri+teslaMeterURI, &res); err != nil { + var res powerwall.MeterResponse + if err := m.GetJSON(m.uri+powerwall.MeterURI, &res); err != nil { return 0, err } @@ -179,8 +150,8 @@ func (m *Tesla) totalEnergy() (float64, error) { // batterySoC implements the api.Battery interface func (m *Tesla) batterySoC() (float64, error) { - var res teslaBatteryResponse - err := m.GetJSON(m.uri+teslaBatteryURI, &res) + var res powerwall.BatteryResponse + err := m.GetJSON(m.uri+powerwall.BatteryURI, &res) return res.Percentage, err }