Porsche: remove deprecated mobile api (#8349)

This commit is contained in:
andig 2023-06-07 18:59:26 +02:00 • committed by GitHub
parent 44c17a60f9
commit 2861266fd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 26 additions and 276 deletions

View file

@ -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,

View file

@ -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,
}),
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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