BMW/Mini: add regions support (#9865)

This commit is contained in:
andig 2023-09-13 23:03:23 +02:00 • committed by GitHub
parent 3524066604
commit 8abaea79b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 17 deletions

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package bmw
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/evcc-io/evcc/util"
@ -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
@ -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,
@ -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,

View file

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

42
vehicle/bmw/param.go Normal file
View file

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