diff --git a/README.md b/README.md index 3150a206b8..4c67e1db67 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ evcc is an extensible EV Charge Controller and home energy management system. Fe - Sunspec-compatible inverter or home battery devices: Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC, ... - and various others: Discovergy, Tesla PowerWall, LG ESS HOME, OpenEMS (FENECON) - [vehicle](https://docs.evcc.io/docs/devices/vehicles) integration (state of charge, remote charge, battery and preconditioning status): - - Audi, BMW, Citroën, Dacia, Fiat, Ford, Hyundai, Jaguar, Kia, Landrover, ~~Mercedes~~, Mini, Nissan, Opel, Peugeot, Porsche, Renault, Seat, Smart, Skoda, Tesla, Volkswagen, Volvo, ... + - Audi, BMW, Citroën, Dacia, Fiat, Ford, Genesis, Hyundai, Jaguar, Kia, Landrover, ~~Mercedes~~, Mini, Nissan, Opel, Peugeot, Porsche, Renault, Seat, Smart, Skoda, Tesla, Volkswagen, Volvo, ... - Services: OVMS, Tronity - Scooters: Niu, ~~Silence~~ - [plugins](https://docs.evcc.io/docs/reference/plugins) for integrating with any charger/ meter/ vehicle: diff --git a/templates/definition/vehicle/genesis.yaml b/templates/definition/vehicle/genesis.yaml new file mode 100644 index 0000000000..931ec017ed --- /dev/null +++ b/templates/definition/vehicle/genesis.yaml @@ -0,0 +1,14 @@ +template: genesis +products: + - brand: Genesis + description: + generic: Bluelink +params: + - preset: vehicle-base + - preset: vehicle-language + - preset: vehicle-identify +render: | + type: genesis + {{ include "vehicle-base" . }} + {{ include "vehicle-language" . }} + {{ include "vehicle-identify" . }} diff --git a/vehicle/bluelink.go b/vehicle/bluelink.go index a85d9998fb..3c09e9b096 100644 --- a/vehicle/bluelink.go +++ b/vehicle/bluelink.go @@ -20,12 +20,15 @@ type Bluelink struct { func init() { registry.Add("kia", NewKiaFromConfig) registry.Add("hyundai", NewHyundaiFromConfig) + registry.Add("genesis", NewGenesisFromConfig) } // NewHyundaiFromConfig creates a new vehicle func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) { settings := bluelink.Config{ - URI: "https://prd.eu-ccapi.hyundai.com:8080", + AuthURI: "https://prd.eu-ccapi.hyundai.com:8080", + APIURI: "https://prd.eu-ccapi.hyundai.com:8080", + RedirectPath: "/api/v1/user/oauth2/redirect", BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==", CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654", CCSPApplicationID: bluelink.HyundaiAppID, @@ -41,7 +44,9 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) { // NewKiaFromConfig creates a new vehicle func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) { settings := bluelink.Config{ - URI: "https://prd.eu-ccapi.kia.com:8080", + AuthURI: "https://prd.eu-ccapi.kia.com:8080", + APIURI: "https://prd.eu-ccapi.kia.com:8080", + RedirectPath: "/api/v1/user/oauth2/redirect", BasicToken: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==", CCSPServiceID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a", CCSPApplicationID: bluelink.KiaAppID, @@ -54,6 +59,24 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) { return newBluelinkFromConfig("kia", other, settings) } +// NewGenesisFromConfig creates a new vehicle +func NewGenesisFromConfig(other map[string]interface{}) (api.Vehicle, error) { + settings := bluelink.Config{ + AuthURI: "https://accounts-eu.genesis.com", + APIURI: "https://prd-eu-ccapi.genesis.com", + RedirectPath: "/realms/eugenesisidm/ga-api/redirect2", + BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==", + CCSPServiceID: "3020afa2-30ff-412a-aa51-d28fbe901e10", + CCSPApplicationID: bluelink.GenesisAppID, + AuthClientID: "f11f2b86-e0e7-4851-90df-5600b01d8b70", + BrandAuthUrl: "https://accounts-eu.genesis.com/realms/eugenesisidm/protocol/openid-connect/auth?client_id=%s&scope=openid+profile+email+phone&response_type=code&hkid_session_reset=true&redirect_uri=%s/api/v1/user/integration/redirect/login&ui_locales=%s&state=%s:%s", + PushType: "GCM", + Cfb: "RFtoRq/vDXJmRndoZaZQyYo3/qFLtVReW8P7utRPcc0ZxOzOELm9mexvviBk/qqIp4A=", + } + + return newBluelinkFromConfig("genesis", other, settings) +} + // newBluelinkFromConfig creates a new Vehicle func newBluelinkFromConfig(brand string, other map[string]interface{}, settings bluelink.Config) (api.Vehicle, error) { cc := struct { @@ -80,7 +103,7 @@ func newBluelinkFromConfig(brand string, other map[string]interface{}, settings return nil, err } - api := bluelink.NewAPI(log, settings.URI, identity.Request) + api := bluelink.NewAPI(log, settings.APIURI, identity.Request) vehicle, err := ensureVehicleEx( cc.VIN, api.Vehicles, diff --git a/vehicle/bluelink/api.go b/vehicle/bluelink/api.go index 339e1808ef..4c2d41945e 100644 --- a/vehicle/bluelink/api.go +++ b/vehicle/bluelink/api.go @@ -27,7 +27,7 @@ const ( // ErrAuthFail indicates authorization failure var ErrAuthFail = errors.New("authorization failed") -// API implements the Kia/Hyundai bluelink api. +// API implements the Kia/Hyundai/Genesis bluelink api. type API struct { *request.Helper baseURI string diff --git a/vehicle/bluelink/identity.go b/vehicle/bluelink/identity.go index 4aaf711b26..3d417c922b 100644 --- a/vehicle/bluelink/identity.go +++ b/vehicle/bluelink/identity.go @@ -33,7 +33,9 @@ const ( // Config is the bluelink API configuration type Config struct { - URI string + AuthURI string + APIURI string + RedirectPath string AuthClientID string // v2 BrandAuthUrl string // v2 BasicToken string @@ -93,7 +95,7 @@ func (v *Identity) getDeviceID() (string, error) { } } - req, err := request.New(http.MethodPost, v.config.URI+DeviceIdURL, request.MarshalJSON(data), headers) + req, err := request.New(http.MethodPost, v.config.APIURI+DeviceIdURL, request.MarshalJSON(data), headers) if err == nil { err = v.DoJSON(req, &res) } @@ -113,9 +115,9 @@ func (v *Identity) getCookies() (cookieClient *request.Helper, err error) { uri := fmt.Sprintf( "%s/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect", - v.config.URI, + v.config.APIURI, v.config.CCSPServiceID, - v.config.URI, + v.config.AuthURI, ) resp, err := cookieClient.Get(uri) @@ -131,7 +133,7 @@ func (v *Identity) setLanguage(cookieClient *request.Helper, language string) er "lang": language, } - req, err := request.New(http.MethodPost, v.config.URI+LanguageURL, request.MarshalJSON(data), request.JSONEncoding) + req, err := request.New(http.MethodPost, v.config.APIURI+LanguageURL, request.MarshalJSON(data), request.JSONEncoding) if err == nil { var resp *http.Response if resp, err = cookieClient.Do(req); err == nil { @@ -143,7 +145,7 @@ func (v *Identity) setLanguage(cookieClient *request.Helper, language string) er } func (v *Identity) brandLogin(cookieClient *request.Helper, user, password string) (string, error) { - req, err := request.New(http.MethodGet, v.config.URI+IntegrationInfoURL, nil, request.JSONEncoding) + req, err := request.New(http.MethodGet, v.config.APIURI+IntegrationInfoURL, nil, request.JSONEncoding) var info struct { UserId string `json:"userId"` @@ -158,7 +160,7 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin var resp *http.Response if err == nil { - uri := fmt.Sprintf(v.config.BrandAuthUrl, v.config.AuthClientID, v.config.URI, "en", info.ServiceId, info.UserId) + uri := fmt.Sprintf(v.config.BrandAuthUrl, v.config.AuthClientID, v.config.APIURI, "en", info.ServiceId, info.UserId) req, err = request.New(http.MethodGet, uri, nil) if err == nil { @@ -223,7 +225,7 @@ func (v *Identity) brandLogin(cookieClient *request.Helper, user, password strin "intUserId": "", } - req, err = request.New(http.MethodPost, v.config.URI+SilentSigninURL, request.MarshalJSON(data), request.JSONEncoding) + req, err = request.New(http.MethodPost, v.config.AuthURI+SilentSigninURL, request.MarshalJSON(data), request.JSONEncoding) if err == nil { req.Header.Set("ccsp-service-id", v.config.CCSPServiceID) cookieClient.CheckRedirect = request.DontFollow @@ -252,7 +254,7 @@ func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password st "password": password, } - req, err := request.New(http.MethodPost, v.config.URI+LoginURL, request.MarshalJSON(data), request.JSONEncoding) + req, err := request.New(http.MethodPost, v.config.APIURI+LoginURL, request.MarshalJSON(data), request.JSONEncoding) if err != nil { return "", err } @@ -284,13 +286,14 @@ func (v *Identity) exchangeCode(accCode string) (oauth.Token, error) { data := url.Values{ "grant_type": {"authorization_code"}, - "redirect_uri": {v.config.URI + "/api/v1/user/oauth2/redirect"}, + "redirect_uri": {v.config.AuthURI + v.config.RedirectPath}, "code": {accCode}, + "client_id": {v.config.CCSPServiceID}, } var token oauth.Token - req, err := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers) + req, err := request.New(http.MethodPost, v.config.APIURI+TokenURL, strings.NewReader(data.Encode()), headers) if err == nil { err = v.DoJSON(req, &token) } @@ -310,9 +313,10 @@ func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { "grant_type": {"refresh_token"}, "redirect_uri": {"https://www.getpostman.com/oauth2/callback"}, "refresh_token": {token.RefreshToken}, + "client_id": {v.config.CCSPServiceID}, } - req, err := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers) + req, err := request.New(http.MethodPost, v.config.APIURI+TokenURL, strings.NewReader(data.Encode()), headers) var res oauth.Token if err == nil { diff --git a/vehicle/bluelink/params.go b/vehicle/bluelink/params.go index 6f850bb447..dd58fee76b 100644 --- a/vehicle/bluelink/params.go +++ b/vehicle/bluelink/params.go @@ -3,4 +3,5 @@ package bluelink const ( KiaAppID = "a2b8469b-30a3-4361-8e13-6fceea8fbe74" HyundaiAppID = "014d2225-8495-4735-812d-2616334fd15d" + GenesisAppID = "f11f2b86-e0e7-4851-90df-5600b01d8b70" )