From 348b3f9030189b7a6639bf45e42c607d59443b9a Mon Sep 17 00:00:00 2001 From: Andreas Linde <42185+DerAndereAndi@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:47:26 +0100 Subject: [PATCH] Updates to Porsche Vehicle API (#512) * Added Remaining Range to Porsche * Added finding VIN --- vehicle/config_test.go | 1 + vehicle/porsche.go | 77 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/vehicle/config_test.go b/vehicle/config_test.go index ef41b25b27..503aae4231 100644 --- a/vehicle/config_test.go +++ b/vehicle/config_test.go @@ -17,6 +17,7 @@ func TestVehicles(t *testing.T) { "Missing required parameter", // Renault "error connecting: Network Error", "unexpected status: 401", + "could not obtain token", // Porsche } for _, tmpl := range test.ConfigTemplates("vehicle") { diff --git a/vehicle/porsche.go b/vehicle/porsche.go index b297f4238b..06853adf01 100644 --- a/vehicle/porsche.go +++ b/vehicle/porsche.go @@ -44,6 +44,14 @@ type porscheVehicleResponse struct { Unit string Value float64 } + RemainingRanges struct { + ElectricalRange struct { + Distance struct { + Unit string + Value float64 + } + } + } } } @@ -54,7 +62,7 @@ type Porsche struct { user, password, vin string token string tokenValid time.Time - chargeStateG func() (float64, error) + chargerG func() (interface{}, error) } func init() { @@ -72,6 +80,10 @@ func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { Cache: interval, } + log := util.NewLogger("porsche") + + var err error + if err := util.DecodeOther(other, &cc); err != nil { return nil, err } @@ -84,14 +96,25 @@ func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { vin: strings.ToUpper(cc.VIN), } - v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() + if err == nil { + err = v.authFlow() + } + + if err == nil && cc.VIN == "" { + v.vin, err = findVehicle(v.vehicles()) + if err == nil { + log.DEBUG.Printf("found vehicle: %v", v.vin) + } + } + + v.chargerG = provider.NewCached(v.chargeState, cc.Cache).InterfaceGetter() - return v, nil + return v, err } // login with a my Porsche account // looks like the backend is using a PingFederate Server with OAuth2 -func (v *Porsche) login(user, password string) error { +func (v *Porsche) authFlow() error { jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { return err @@ -127,8 +150,8 @@ func (v *Porsche) login(user, password string) error { "resume": []string{resume}, "thirdPartyId": []string{thirdPartyID}, "state": []string{state}, - "username": []string{user}, - "password": []string{password}, + "username": []string{v.user}, + "password": []string{v.password}, "keeploggedin": []string{"false"}, } @@ -206,7 +229,7 @@ func (v *Porsche) login(user, password string) error { func (v *Porsche) request(uri string) (*http.Request, error) { if v.token == "" || time.Since(v.tokenValid) > 0 { - if err := v.login(v.user, v.password); err != nil { + if err := v.authFlow(); err != nil { return nil, err } } @@ -218,8 +241,27 @@ func (v *Porsche) request(uri string) (*http.Request, error) { return req, err } +func (v *Porsche) vehicles() (res []string, err error) { + uri := "https://connect-portal.porsche.com/core/api/v3/de/de_DE/vehicles" + req, err := v.request(uri) + + var vehicles []struct { + VIN string + } + + if err == nil { + err = v.DoJSON(req, &vehicles) + + for _, v := range vehicles { + res = append(res, v.VIN) + } + } + + return res, err +} + // chargeState implements the Vehicle.ChargeState interface -func (v *Porsche) chargeState() (float64, error) { +func (v *Porsche) chargeState() (interface{}, error) { uri := fmt.Sprintf("%s/vehicles/%s", porscheAPI, v.vin) req, err := v.request(uri) if err != nil { @@ -229,10 +271,25 @@ func (v *Porsche) chargeState() (float64, error) { var pr porscheVehicleResponse err = v.DoJSON(req, &pr) - return pr.CarControlData.BatteryLevel.Value, err + return pr, err } // ChargeState implements the Vehicle.ChargeState interface func (v *Porsche) ChargeState() (float64, error) { - return v.chargeStateG() + res, err := v.chargerG() + if res, ok := res.(porscheVehicleResponse); err == nil && ok { + return res.CarControlData.BatteryLevel.Value, nil + } + + return 0, err +} + +// Range implements the Vehicle.Range interface +func (v *Porsche) Range() (int64, error) { + res, err := v.chargerG() + if res, ok := res.(porscheVehicleResponse); err == nil && ok { + return int64(res.CarControlData.RemainingRanges.ElectricalRange.Distance.Value), nil + } + + return 0, err }