Skip to content

Commit

Permalink
BMW/Mini: add regions support (#9865)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Sep 13, 2023
1 parent 3524066 commit 8abaea7
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 17 deletions.
11 changes: 11 additions & 0 deletions templates/definition/vehicle/bmw.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ params:
- preset: vehicle-identify
- name: vin
example: WBMW...
- name: region
description:
de: Region
en: Region
values:
- NA
- EU
default: EU
render: |
type: bmw
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}
{{- if ne .region "EU" }}
region: {{ .region }}
{{- end }}
11 changes: 11 additions & 0 deletions templates/definition/vehicle/mini.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ params:
- preset: vehicle-identify
- name: vin
example: WBMW...
- name: region
description:
de: Region
en: Region
values:
- NA
- EU
default: EU
render: |
type: mini
{{ include "vehicle-base" . }}
{{ include "vehicle-identify" . }}
{{- if ne .region "EU" }}
region: {{ .region }}
{{- end }}
8 changes: 5 additions & 3 deletions vehicle/bmw.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ func NewBMWMiniFromConfig(brand string, other map[string]interface{}) (api.Vehic
cc := struct {
embed `mapstructure:",squash"`
User, Password, VIN string
Region string
Cache time.Duration
}{
Cache: interval,
Region: "EU",
Cache: interval,
}

if err := util.DecodeOther(other, &cc); err != nil {
Expand All @@ -52,14 +54,14 @@ func NewBMWMiniFromConfig(brand string, other map[string]interface{}) (api.Vehic
}

log := util.NewLogger(brand).Redact(cc.User, cc.Password, cc.VIN)
identity := bmw.NewIdentity(log)
identity := bmw.NewIdentity(log, cc.Region)

ts, err := identity.Login(cc.User, cc.Password)
if err != nil {
return nil, err
}

api := bmw.NewAPI(log, brand, ts)
api := bmw.NewAPI(log, brand, cc.Region, ts)

cc.VIN, err = ensureVehicle(cc.VIN, api.Vehicles)

Expand Down
13 changes: 6 additions & 7 deletions vehicle/bmw/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bmw
import (
"fmt"
"net/http"
"strings"
"time"

"github.com/evcc-io/evcc/util"
Expand All @@ -13,21 +14,19 @@ import (
// https://github.com/bimmerconnected/bimmer_connected
// https://github.com/TA2k/ioBroker.bmw

const (
CocoApiURI = "https://cocoapi.bmwgroup.com"
)

// API is an api.Vehicle implementation for BMW cars
type API struct {
*request.Helper
xUserAgent string
region string
}

// NewAPI creates a new vehicle
func NewAPI(log *util.Logger, brand string, identity oauth2.TokenSource) *API {
func NewAPI(log *util.Logger, brand, region string, identity oauth2.TokenSource) *API {
v := &API{
Helper: request.NewHelper(log),
xUserAgent: fmt.Sprintf("android(SP1A.210812.016.C1);%s;99.0.0(99999);row", brand),
region: strings.ToUpper(region),
}

// replace client transport with authenticated transport
Expand All @@ -42,7 +41,7 @@ func NewAPI(log *util.Logger, brand string, identity oauth2.TokenSource) *API {
// Vehicles implements returns the /user/vehicles api
func (v *API) Vehicles() ([]string, error) {
var res []Vehicle
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles?apptimezone=120&appDateTime=%d", CocoApiURI, time.Now().UnixMilli())
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles?apptimezone=120&appDateTime=%d", regions[v.region].CocoApiURI, time.Now().UnixMilli())

req, err := request.New(http.MethodGet, uri, nil, map[string]string{
"Content-Type": request.JSONContent,
Expand All @@ -67,7 +66,7 @@ func (v *API) Vehicles() ([]string, error) {
// Status implements the /user/vehicles/<vin>/status api
func (v *API) Status(vin string) (VehicleStatus, error) {
var res VehicleStatus
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=%d", CocoApiURI, time.Now().UnixMilli())
uri := fmt.Sprintf("%s/eadrax-vcs/v4/vehicles/state?apptimezone=120&appDateTime=%d", regions[v.region].CocoApiURI, time.Now().UnixMilli())

req, err := request.New(http.MethodGet, uri, nil, map[string]string{
"Content-Type": request.JSONContent,
Expand Down
15 changes: 8 additions & 7 deletions vehicle/bmw/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,19 @@ import (
)

const (
AuthURI = "https://customer.bmwgroup.com/gcdm/oauth"
RedirectURI = "com.bmw.connected://oauth"
)

type Identity struct {
*request.Helper
region string
}

// NewIdentity creates BMW identity
func NewIdentity(log *util.Logger) *Identity {
func NewIdentity(log *util.Logger, region string) *Identity {
v := &Identity{
Helper: request.NewHelper(log),
region: strings.ToUpper(region),
}

return v
Expand All @@ -52,10 +53,10 @@ func (v *Identity) Login(user, password string) (oauth2.TokenSource, error) {
}

data := url.Values{
"client_id": {"31c357a0-7a1d-4590-aa99-33b97244d048"},
"client_id": {regions[v.region].ClientID},
"response_type": {"code"},
"redirect_uri": {RedirectURI},
"state": {"cwU-gIE27j67poy2UcL3KQ"},
"state": {regions[v.region].State},
"scope": {"openid profile email offline_access smacc vehicle_data perseus dlm svds cesim vsapi remote_services fupo authenticate_user"},
"nonce": {"login_nonce"},
"code_challenge_method": {"S256"},
Expand All @@ -65,7 +66,7 @@ func (v *Identity) Login(user, password string) (oauth2.TokenSource, error) {
"grant_type": {"authorization_code"},
}

uri := fmt.Sprintf("%s/authenticate", AuthURI)
uri := fmt.Sprintf("%s/oauth/authenticate", regions[v.region].AuthURI)
req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), request.URLEncoding)
if err != nil {
return nil, err
Expand Down Expand Up @@ -133,10 +134,10 @@ func (v *Identity) Login(user, password string) (oauth2.TokenSource, error) {
}

func (v *Identity) retrieveToken(data url.Values) (*oauth2.Token, error) {
uri := fmt.Sprintf("%s/token", AuthURI)
uri := fmt.Sprintf("%s/oauth/token", regions[v.region].AuthURI)
req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), map[string]string{
"Content-Type": request.FormContent,
"Authorization": "Basic MzFjMzU3YTAtN2ExZC00NTkwLWFhOTktMzNiOTcyNDRkMDQ4OmMwZTMzOTNkLTcwYTItNGY2Zi05ZDNjLTg1MzBhZjY0ZDU1Mg==",
"Authorization": regions[v.region].Authorization,
})

var tok oauth.Token
Expand Down
42 changes: 42 additions & 0 deletions vehicle/bmw/param.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package bmw

type (
Region struct {
AuthURI, CocoApiURI string
Token
Authenticate
}
Token struct {
Authorization string
}
Authenticate struct {
ClientID, State string
}
)

var regions = map[string]Region{
"NA": {
"https://login.bmwusa.com/gcdm",
"https://cocoapi.bmwgroup.us",
Token{
Authorization: "Basic NTQzOTRhNGItYjZjMS00NWZlLWI3YjItOGZkM2FhOTI1M2FhOmQ5MmYzMWMwLWY1NzktNDRmNS1hNzdkLTk2NmY4ZjAwZTM1MQ==",
// Verifier: "KDarcVUpgymBDCgHDH0PwwMfzycDxu1joeklioOhwXA",
},
Authenticate{
ClientID: "54394a4b-b6c1-45fe-b7b2-8fd3aa9253aa",
State: "rgastJbZsMtup49-Lp0FMQ",
},
},
"EU": {
"https://customer.bmwgroup.com/gcdm",
"https://cocoapi.bmwgroup.com",
Token{
Authorization: "Basic MzFjMzU3YTAtN2ExZC00NTkwLWFhOTktMzNiOTcyNDRkMDQ4OmMwZTMzOTNkLTcwYTItNGY2Zi05ZDNjLTg1MzBhZjY0ZDU1Mg==",
// Verifier: "7PsmfPS5MpaNt0jEcPpi-B7M7u0gs1Nzw6ex0Y9pa-0",
},
Authenticate{
ClientID: "31c357a0-7a1d-4590-aa99-33b97244d048",
State: "cEG9eLAIi6Nv-aaCAniziE_B6FPoobva3qr5gukilYw",
},
},
}

0 comments on commit 8abaea7

Please sign in to comment.