Skip to content

Commit

Permalink
Updates to Porsche Vehicle API (#512)
Browse files Browse the repository at this point in the history
* Added Remaining Range to Porsche
* Added finding VIN
  • Loading branch information
DerAndereAndi committed Dec 8, 2020
1 parent 3455042 commit 348b3f9
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 10 deletions.
1 change: 1 addition & 0 deletions vehicle/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
77 changes: 67 additions & 10 deletions vehicle/porsche.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ type porscheVehicleResponse struct {
Unit string
Value float64
}
RemainingRanges struct {
ElectricalRange struct {
Distance struct {
Unit string
Value float64
}
}
}
}
}

Expand All @@ -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() {
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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"},
}

Expand Down Expand Up @@ -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
}
}
Expand All @@ -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 {
Expand All @@ -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
}

0 comments on commit 348b3f9

Please sign in to comment.