From fdb6bd99d44ee120a7a583e7fe30a43fc80c9849 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 7 Jun 2023 18:59:26 +0200 Subject: [PATCH] Porsche: remove deprecated mobile api (#8349) --- vehicle/porsche.go | 10 +-- vehicle/porsche/api.go | 3 +- vehicle/porsche/api_emobility.go | 4 +- vehicle/porsche/api_mobile.go | 121 ------------------------------- vehicle/porsche/identity.go | 70 ++++++------------ vehicle/porsche/provider.go | 94 +----------------------- 6 files changed, 26 insertions(+), 276 deletions(-) delete mode 100644 vehicle/porsche/api_mobile.go diff --git a/vehicle/porsche.go b/vehicle/porsche.go index 7d18d36b9b..0eb50d0b54 100644 --- a/vehicle/porsche.go +++ b/vehicle/porsche.go @@ -48,16 +48,8 @@ func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { } api := porsche.NewAPI(log, identity.DefaultSource) - mobile := porsche.NewMobileAPI(log, identity.MobileSource) cc.VIN, err = ensureVehicle(cc.VIN, func() ([]string, error) { - mobileVehicles, err := mobile.Vehicles() - if err == nil { - return lo.Map(mobileVehicles, func(v porsche.StatusResponseMobile, _ int) string { - return v.VIN - }), err - } - vehicles, err := api.Vehicles() return lo.Map(vehicles, func(v porsche.Vehicle, _ int) string { return v.VIN @@ -80,7 +72,7 @@ func NewPorscheFromConfig(other map[string]interface{}) (api.Vehicle, error) { return nil, err } - provider := porsche.NewProvider(log, api, emobility, mobile, cc.VIN, capabilities.CarModel, cc.Cache) + provider := porsche.NewProvider(log, api, emobility, cc.VIN, capabilities.CarModel, cc.Cache) v := &Porsche{ embed: &cc.embed, diff --git a/vehicle/porsche/api.go b/vehicle/porsche/api.go index 54e243a3ec..3ef1a3426e 100644 --- a/vehicle/porsche/api.go +++ b/vehicle/porsche/api.go @@ -37,8 +37,7 @@ func NewAPI(log *util.Logger, identity oauth2.TokenSource) *API { Base: v.Client.Transport, }, Decorator: transport.DecorateHeaders(map[string]string{ - "apikey": OAuth2Config.ClientID, - "x-client-id": "52064df8-6daa-46f7-bc9e-e3232622ab26", + "apikey": OAuth2Config.ClientID, }), } diff --git a/vehicle/porsche/api_emobility.go b/vehicle/porsche/api_emobility.go index f5f8bf260c..7d8deacc5f 100644 --- a/vehicle/porsche/api_emobility.go +++ b/vehicle/porsche/api_emobility.go @@ -36,7 +36,7 @@ func NewEmobilityAPI(log *util.Logger, identity oauth2.TokenSource) *EmobilityAP func (v *EmobilityAPI) Capabilities(vin string) (CapabilitiesResponse, error) { var res CapabilitiesResponse - uri := fmt.Sprintf("https://api.porsche.com/e-mobility/vcs/capabilities/%s", vin) + uri := fmt.Sprintf("%s/e-mobility/vcs/capabilities/%s", ApiURI, vin) err := v.GetJSON(uri, &res) return res, err } @@ -45,7 +45,7 @@ func (v *EmobilityAPI) Capabilities(vin string) (CapabilitiesResponse, error) { func (v *EmobilityAPI) Status(vin, model string) (EmobilityResponse, error) { var res EmobilityResponse - uri := fmt.Sprintf("https://api.porsche.com/e-mobility/de/de_DE/%s/%s?timezone=Europe/Berlin", model, vin) + uri := fmt.Sprintf("%s/e-mobility/de/de_DE/%s/%s?timezone=Europe/Berlin", ApiURI, model, vin) err := v.GetJSON(uri, &res) if err != nil && res.PcckErrorMessage != "" { err = errors.New(res.PcckErrorMessage) diff --git a/vehicle/porsche/api_mobile.go b/vehicle/porsche/api_mobile.go deleted file mode 100644 index 195d6b8662..0000000000 --- a/vehicle/porsche/api_mobile.go +++ /dev/null @@ -1,121 +0,0 @@ -package porsche - -import ( - "fmt" - "strings" - - "github.com/evcc-io/evcc/util" - "github.com/evcc-io/evcc/util/request" - "github.com/evcc-io/evcc/util/transport" - "golang.org/x/oauth2" -) - -const ( - MobileApiURI = "https://api.ppa.porsche.com" - - // vehicle status elements - ACV_STATE = "ACV_STATE" - BATTERY_CHARGING_STATE = "BATTERY_CHARGING_STATE" - BATTERY_LEVEL = "BATTERY_LEVEL" - BATTERY_TYPE = "BATTERY_TYPE" - BLEID_DDADATA = "BLEID_DDADATA" - CAR_ALARMS_HISTORY = "CAR_ALARMS_HISTORY" - CHARGING_PROFILES = "CHARGING_PROFILES" - CLIMATIZER_STATE = "CLIMATIZER_STATE" - E_RANGE = "E_RANGE" - FUEL_LEVEL = "FUEL_LEVEL" - FUEL_RESERVE = "FUEL_RESERVE" - GLOBAL_PRIVACY_MODE = "GLOBAL_PRIVACY_MODE" - GPS_LOCATION = "GPS_LOCATION" - HEATING_STATE = "HEATING_STATE" - INTERMEDIATE_SERVICE_RANGE = "INTERMEDIATE_SERVICE_RANGE" - INTERMEDIATE_SERVICE_TIME = "INTERMEDIATE_SERVICE_TIME" - LOCATION_ALARMS = "LOCATION_ALARMS" - LOCATION_ALARMS_HISTORY = "LOCATION_ALARMS_HISTORY" - LOCK_STATE_VEHICLE = "LOCK_STATE_VEHICLE" - MAIN_SERVICE_RANGE = "MAIN_SERVICE_RANGE" - MAIN_SERVICE_TIME = "MAIN_SERVICE_TIME" - MILEAGE = "MILEAGE" - OIL_LEVEL_CURRENT = "OIL_LEVEL_CURRENT" - OIL_LEVEL_MAX = "OIL_LEVEL_MAX" - OIL_LEVEL_MIN_WARNING = "OIL_LEVEL_MIN_WARNING" - OIL_SERVICE_RANGE = "OIL_SERVICE_RANGE" - OIL_SERVICE_TIME = "OIL_SERVICE_TIME" - OPEN_STATE_CHARGE_FLAP_LEFT = "OPEN_STATE_CHARGE_FLAP_LEFT" - OPEN_STATE_CHARGE_FLAP_RIGHT = "OPEN_STATE_CHARGE_FLAP_RIGHT" - OPEN_STATE_DOOR_FRONT_LEFT = "OPEN_STATE_DOOR_FRONT_LEFT" - OPEN_STATE_DOOR_FRONT_RIGHT = "OPEN_STATE_DOOR_FRONT_RIGHT" - OPEN_STATE_DOOR_REAR_LEFT = "OPEN_STATE_DOOR_REAR_LEFT" - OPEN_STATE_DOOR_REAR_RIGHT = "OPEN_STATE_DOOR_REAR_RIGHT" - OPEN_STATE_LID_FRONT = "OPEN_STATE_LID_FRONT" - OPEN_STATE_LID_REAR = "OPEN_STATE_LID_REAR" - OPEN_STATE_SERVICE_FLAP = "OPEN_STATE_SERVICE_FLAP" - OPEN_STATE_SPOILER = "OPEN_STATE_SPOILER" - OPEN_STATE_SUNROOF = "OPEN_STATE_SUNROOF" - OPEN_STATE_TOP = "OPEN_STATE_TOP" - OPEN_STATE_WINDOW_FRONT_LEFT = "OPEN_STATE_WINDOW_FRONT_LEFT" - OPEN_STATE_WINDOW_FRONT_RIGHT = "OPEN_STATE_WINDOW_FRONT_RIGHT" - OPEN_STATE_WINDOW_REAR_LEFT = "OPEN_STATE_WINDOW_REAR_LEFT" - OPEN_STATE_WINDOW_REAR_RIGHT = "OPEN_STATE_WINDOW_REAR_RIGHT" - PARKING_LIGHT = "PARKING_LIGHT" - RANGE = "RANGE" - REMOTE_ACCESS_AUTHORIZATION = "REMOTE_ACCESS_AUTHORIZATION" - SERVICE_PREDICTIONS = "SERVICE_PREDICTIONS" - SPEED_ALARMS = "SPEED_ALARMS" - SPEED_ALARMS_HISTORY = "SPEED_ALARMS_HISTORY" - THEFT_MODE = "THEFT_MODE" - TIMERS = "TIMERS" - TRIP_STATISTICS_CYCLIC = "TRIP_STATISTICS_CYCLIC" - TRIP_STATISTICS_LONG_TERM = "TRIP_STATISTICS_LONG_TERM" - TRIP_STATISTICS_SHORT_TERM = "TRIP_STATISTICS_SHORT_TERM" - VALET_ALARM = "VALET_ALARM" - VALET_ALARM_HISTORY = "VALET_ALARM_HISTORY" - VTS_MODES = "VTS_MODES" -) - -// API is an api.Vehicle implementation for Porsche PHEV cars -type MobileAPI struct { - *request.Helper -} - -// NewAPI creates a new vehicle -func NewMobileAPI(log *util.Logger, identity oauth2.TokenSource) *MobileAPI { - v := &MobileAPI{ - Helper: request.NewHelper(log), - } - - v.Client.Transport = &transport.Decorator{ - Base: &oauth2.Transport{ - Source: identity, - Base: v.Client.Transport, - }, - Decorator: transport.DecorateHeaders(map[string]string{ - "apikey": OAuth2Config.ClientID, - "x-client-id": "52064df8-6daa-46f7-bc9e-e3232622ab26", - }), - } - - return v -} - -// Vehicles implements the vehicle list response -func (v *MobileAPI) Vehicles() ([]StatusResponseMobile, error) { - var res []StatusResponseMobile - uri := fmt.Sprintf("%s/app/connect/v1/vehicles", MobileApiURI) - err := v.GetJSON(uri, &res) - return res, err -} - -// Status implements the vehicle status response -func (v *MobileAPI) Status(vin string, items []string) (StatusResponseMobile, error) { - var res StatusResponseMobile - - mf := make([]string, 0, len(items)) - for _, i := range items { - mf = append(mf, "mf="+i) - } - - uri := fmt.Sprintf("%s/app/connect/v1/vehicles/%s?%s", MobileApiURI, vin, strings.Join(mf, "&")) - err := v.GetJSON(uri, &res) - return res, err -} diff --git a/vehicle/porsche/identity.go b/vehicle/porsche/identity.go index 49b5bfef39..69657b7f75 100644 --- a/vehicle/porsche/identity.go +++ b/vehicle/porsche/identity.go @@ -7,7 +7,6 @@ import ( "net/http" "net/http/cookiejar" "net/url" - "strings" "github.com/evcc-io/evcc/util" "github.com/evcc-io/evcc/util/oauth" @@ -39,21 +38,14 @@ var ( Endpoint: OAuth2Config.Endpoint, Scopes: []string{"openid"}, } - - MobileOAuth2Config = &oauth2.Config{ - ClientID: "L20OiZ0kBgWt958NWbuCB8gb970y6V6U", - RedirectURL: "One-Product-App://porsche-id/oauth2redirect", - Endpoint: OAuth2Config.Endpoint, - Scopes: []string{"openid", "magiclink", "mbb"}, - } ) // Identity is the Porsche Identity client type Identity struct { *request.Helper - user, password string - defaultToken, emobilityToken, mobileToken *oauth2.Token - DefaultSource, EmobilitySource, MobileSource oauth2.TokenSource + user, password string + defaultToken, emobilityToken *oauth2.Token + DefaultSource, EmobilitySource oauth2.TokenSource } // NewIdentity creates Porsche identity @@ -73,7 +65,6 @@ func (v *Identity) Login() error { if err == nil { v.DefaultSource = oauth.RefreshTokenSource(v.defaultToken, v) v.EmobilitySource = oauth.RefreshTokenSource(v.emobilityToken, &emobilityAdapter{v}) - v.MobileSource = oauth.RefreshTokenSource(v.mobileToken, &mobileAdapter{v}) } return err @@ -98,9 +89,19 @@ func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { v.Client.CheckRedirect = nil }() + preLogin := url.Values{ + "sec": []string{""}, + "resume": []string{""}, + "thirdPartyId": []string{""}, + "state": []string{""}, + "username": []string{v.user}, + "password": []string{v.password}, + "keeploggedin": []string{"false"}, + } + // get the login page uri := fmt.Sprintf("%s/auth/api/v1/de/de_DE/public/login", OAuthURI) - resp, err := v.Get(uri) + resp, err := v.PostForm(uri, preLogin) if err != nil { return nil, err } @@ -111,28 +112,19 @@ func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { return nil, err } - sec := query.Get("sec") - resume := query.Get("resume") - state := query.Get("state") - thirdPartyID := query.Get("thirdPartyId") - dataLoginAuth := url.Values{ - "sec": []string{sec}, - "resume": []string{resume}, - "thirdPartyId": []string{thirdPartyID}, - "state": []string{state}, + "sec": []string{query.Get("sec")}, + "resume": []string{query.Get("resume")}, + "thirdPartyId": []string{query.Get("thirdPartyID")}, + "state": []string{query.Get("state")}, "username": []string{v.user}, "password": []string{v.password}, "keeploggedin": []string{"false"}, } - req, err := request.New(http.MethodPost, uri, strings.NewReader(dataLoginAuth.Encode()), request.URLEncoding) - if err != nil { - return nil, err - } - // process the auth so the session is authenticated - if resp, err = v.Client.Do(req); err != nil { + resp, err = v.PostForm(uri, dataLoginAuth) + if err != nil { return nil, err } resp.Body.Close() @@ -145,10 +137,6 @@ func (v *Identity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { if token, err = v.fetchToken(EmobilityOAuth2Config); err == nil { v.emobilityToken = token } - - if token, err = v.fetchToken(MobileOAuth2Config); err == nil { - v.mobileToken = token - } } return v.defaultToken, err @@ -180,11 +168,7 @@ func (v *Identity) fetchToken(oc *oauth2.Config) (*oauth2.Token, error) { } resp.Body.Close() - rawQuery := resp.Request.URL.RawQuery - if location, ok := strings.CutPrefix(resp.Header.Get("Location"), "One-Product-App://porsche-id/oauth2redirect?"); ok { - rawQuery = location - } - query, err := url.ParseQuery(rawQuery) + query, err := url.ParseQuery(resp.Request.URL.RawQuery) if err != nil { return nil, err } @@ -218,15 +202,3 @@ func (v *emobilityAdapter) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) } return token, err } - -type mobileAdapter struct { - tr *Identity -} - -func (v *mobileAdapter) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) { - token, err := v.tr.RefreshToken(nil) - if err == nil { - token = v.tr.mobileToken - } - return token, err -} diff --git a/vehicle/porsche/provider.go b/vehicle/porsche/provider.go index 37ddbf28fc..c01aa5f060 100644 --- a/vehicle/porsche/provider.go +++ b/vehicle/porsche/provider.go @@ -13,12 +13,11 @@ import ( type Provider struct { statusG func() (StatusResponse, error) emobilityG func() (EmobilityResponse, error) - mobileG func() (StatusResponseMobile, error) wakeup func() error } // NewProvider creates a vehicle api provider -func NewProvider(log *util.Logger, api *API, emobility *EmobilityAPI, mobile *MobileAPI, vin, carModel string, cache time.Duration) *Provider { +func NewProvider(log *util.Logger, api *API, emobility *EmobilityAPI, vin, carModel string, cache time.Duration) *Provider { impl := &Provider{ statusG: provider.Cached(func() (StatusResponse, error) { return api.Status(vin) @@ -31,16 +30,11 @@ func NewProvider(log *util.Logger, api *API, emobility *EmobilityAPI, mobile *Mo return EmobilityResponse{}, errors.New("no car model") }, cache), - mobileG: provider.Cached(func() (StatusResponseMobile, error) { - return mobile.Status(vin, []string{BATTERY_LEVEL, BATTERY_CHARGING_STATE, CLIMATIZER_STATE, E_RANGE, HEATING_STATE, MILEAGE}) - }, cache), - wakeup: func() error { _, _ = api.Status(vin) if carModel != "" { _, _ = emobility.Status(vin, carModel) } - _, _ = mobile.Status(vin, []string{BATTERY_LEVEL}) return nil }, } @@ -52,17 +46,6 @@ var _ api.Battery = (*Provider)(nil) // Soc implements the api.Vehicle interface func (v *Provider) Soc() (float64, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("BATTERY_LEVEL") - if err != nil && err != api.ErrNotAvailable { - return 0, err - } - if err != api.ErrNotAvailable { - return float64(m.Value.Percent), nil - } - } - res3, err := v.emobilityG() if err == nil && res3.BatteryChargeStatus != nil { return float64(res3.BatteryChargeStatus.StateOfChargeInPercentage), nil @@ -80,17 +63,6 @@ var _ api.VehicleRange = (*Provider)(nil) // Range implements the api.VehicleRange interface func (v *Provider) Range() (int64, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("E_RANGE") - if err != nil && err != api.ErrNotAvailable { - return 0, err - } - if err != api.ErrNotAvailable { - return int64(m.Value.Kilometers), nil - } - } - res3, err := v.emobilityG() if err == nil && res3.BatteryChargeStatus != nil { return res3.BatteryChargeStatus.RemainingERange.ValueInKilometers, nil @@ -108,24 +80,6 @@ var _ api.VehicleFinishTimer = (*Provider)(nil) // FinishTime implements the api.VehicleFinishTimer interface func (v *Provider) FinishTime() (time.Time, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("BATTERY_CHARGING_STATE") - if err != nil && err != api.ErrNotAvailable { - return time.Time{}, err - } - - if err != api.ErrNotAvailable { - if m.Value.EndsAt == "" { - if m.Value.LastModified != "" { - return time.Parse(time.RFC3339, m.Value.LastModified) - } - return time.Time{}, nil - } - return time.Parse(time.RFC3339, m.Value.EndsAt) - } - } - res2, err := v.emobilityG() if err == nil { if res2.BatteryChargeStatus == nil { @@ -142,30 +96,6 @@ var _ api.ChargeState = (*Provider)(nil) // Status implements the api.ChargeState interface func (v *Provider) Status() (api.ChargeStatus, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("BATTERY_CHARGING_STATE") - if err != nil && err != api.ErrNotAvailable { - return api.StatusNone, err - } - - if err != api.ErrNotAvailable { - switch m.Value.Status { - case "FAST_CHARGING", "NOT_PLUGGED", "UNKNOWN": - return api.StatusA, nil - case "CHARGING_COMPLETED", "CHARGING_PAUSED", "READY_TO_CHARGE", "SOC_REACHED", - "INITIALISING", "STANDBY", "SUSPENDED", "PLUGGED_LOCKED", "PLUGGED_NOT_LOCKED": - return api.StatusB, nil - case "CHARGING": - return api.StatusC, nil - case "CHARGING_ERROR": - return api.StatusF, nil - default: - return api.StatusNone, errors.New("mobile - unknown charging status: " + m.Value.Status) - } - } - } - res2, err := v.emobilityG() if err == nil { if res2.BatteryChargeStatus == nil { @@ -200,17 +130,6 @@ var _ api.VehicleClimater = (*Provider)(nil) // Climater implements the api.VehicleClimater interface func (v *Provider) Climater() (bool, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("CLIMATIZER_STATE") - if err != nil && err != api.ErrNotAvailable { - return false, err - } - if err != api.ErrNotAvailable { - return m.Value.IsOn, err - } - } - res2, err := v.emobilityG() if err == nil { if res2.BatteryChargeStatus == nil { @@ -234,17 +153,6 @@ var _ api.VehicleOdometer = (*Provider)(nil) // Odometer implements the api.VehicleOdometer interface func (v *Provider) Odometer() (float64, error) { - res, err := v.mobileG() - if err == nil { - m, err := res.MeasurementByKey("MILEAGE") - if err != nil && err != api.ErrNotAvailable { - return 0, err - } - if err != api.ErrNotAvailable { - return float64(m.Value.Kilometers), nil - } - } - res2, err := v.statusG() if err == nil { return res2.Mileage.Value, nil