Skip to content

Commit fab9464

Browse files
authored
Kia (EU): use refresh_token as password (#23523)
1 parent a9ec457 commit fab9464

3 files changed

Lines changed: 95 additions & 21 deletions

File tree

templates/definition/vehicle/kia.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,14 @@ products:
55
generic: Bluelink
66
requirements:
77
description:
8-
en: Some models (e.g. Niro EV) switch internally to 2 phases at low charging currents (< 8A). In cases where the wallbox also measures the phase currents, this leads to undesirable fluctuations in the charging power. The remedy here is to set the minimum charging current to 8A.
9-
de: Manche Modelle (z.B. Niro EV) schalten bei geringen Ladeströmen (< 8A) intern auf 2 Phasen um. In den Fällen, in denen die Wallbox auch die Phasenströme misst, führt das zu unerwünschten Schwankungen der Ladeleistung. Abhilfe schafft hier, den Mindestladestrom auf 8A zu setzen.
8+
en: |
9+
Instead of your account's password, the password field needs to be filled with a `refresh_token` ([instructions](https://github.com/evcc-io/evcc/wiki/Kia:-Refresh%E2%80%90Token-ermitteln#english-version)).
10+
11+
Some models (e.g. Niro EV) switch internally to 2 phases at low charging currents (< 8A). In cases where the wallbox also measures the phase currents, this leads to undesirable fluctuations in the charging power. The remedy here is to set the minimum charging current to 8A.
12+
de: |
13+
Anstelle des Passworts muss in das Passwort-Feld ein `refresh_token` eingetragen werden ([Anleitung](https://github.com/evcc-io/evcc/wiki/Kia:-Refresh%E2%80%90Token-ermitteln)).
14+
15+
Manche Modelle (z.B. Niro EV) schalten bei geringen Ladeströmen (< 8A) intern auf 2 Phasen um. In den Fällen, in denen die Wallbox auch die Phasenströme misst, führt das zu unerwünschten Schwankungen der Ladeleistung. Abhilfe schafft hier, den Mindestladestrom auf 8A zu setzen.
1016
params:
1117
- preset: vehicle-base
1218
- preset: vehicle-language

vehicle/bluelink.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
3333
BrandAuthUrl: "https://eu-account.hyundai.com/auth/realms/euhyundaiidm/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",
3434
PushType: "GCM",
3535
Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=",
36+
Brand: "hyundai",
3637
// for oauth2??
3738
// LoginFormHost: "https://idpconnect-eu.hyundai.com",
3839
// AuthClientID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
@@ -54,6 +55,7 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
5455
PushType: "APNS",
5556
Cfb: "wLTVxwidmH8CfJYBWSnHD6E0huk0ozdiuygB4hLkM5XCgzAL1Dk5sE36d/bx5PFMbZs=",
5657
LoginFormHost: "https://idpconnect-eu.kia.com",
58+
Brand: "kia",
5759
}
5860

5961
return newBluelinkFromConfig("kia", other, settings)

vehicle/bluelink/identity.go

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Config struct {
4242
PushType string
4343
Cfb string
4444
LoginFormHost string
45+
Brand string
4546
}
4647

4748
// Identity implements the Kia/Hyundai bluelink identity.
@@ -247,6 +248,7 @@ func (v *Identity) brandLoginHyundaiEU(cookieClient *request.Helper, user, passw
247248
return code, err
248249
}
249250

251+
/* Unused for now
250252
func (v *Identity) brandLoginKiaEU(user, password string) (string, error) {
251253
cookieClient := request.NewHelper(v.log)
252254
cookieClient.Client.Jar, _ = cookiejar.New(&cookiejar.Options{
@@ -255,7 +257,8 @@ func (v *Identity) brandLoginKiaEU(user, password string) (string, error) {
255257
256258
headers := map[string]string{
257259
"content-type": "application/x-www-form-urlencoded",
258-
"User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
260+
"User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19_CCS_APP_AOS",
261+
// "User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19",
259262
}
260263
261264
data := url.Values{
@@ -334,6 +337,7 @@ func (v *Identity) brandLoginKiaEU(user, password string) (string, error) {
334337
335338
return code, nil
336339
}
340+
*/
337341

338342
func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password string) (string, error) {
339343
data := map[string]interface{}{
@@ -385,6 +389,31 @@ func (v *Identity) exchangeCodeHyundaiEU(accCode string) (*oauth2.Token, error)
385389
return util.TokenWithExpiry(&token), err
386390
}
387391

392+
func (v *Identity) exchangeCodeKiaEURefreshToken(accCode string) (*oauth2.Token, error) {
393+
uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
394+
headers := map[string]string{
395+
"Content-type": "application/x-www-form-urlencoded",
396+
"User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19_CCS_APP_AOS",
397+
}
398+
data := url.Values{
399+
"grant_type": {"refresh_token"},
400+
"refresh_token": {accCode},
401+
"client_id": {v.config.CCSPServiceID},
402+
"client_secret": {"secret"},
403+
}
404+
405+
var token oauth2.Token
406+
407+
req, _ := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers)
408+
err := v.DoJSON(req, &token)
409+
410+
// manually set the refresh token
411+
token.RefreshToken = accCode
412+
413+
return util.TokenWithExpiry(&token), err
414+
}
415+
416+
/* Unused for now
388417
func (v *Identity) exchangeCodeKiaEU(accCode string) (*oauth2.Token, error) {
389418
uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
390419
headers := map[string]string{
@@ -406,26 +435,61 @@ func (v *Identity) exchangeCodeKiaEU(accCode string) (*oauth2.Token, error) {
406435
407436
return util.TokenWithExpiry(&token), err
408437
}
438+
*/
409439

410440
// RefreshToken implements oauth.TokenRefresher
411441
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
412-
headers := map[string]string{
413-
"Authorization": "Basic " + v.config.BasicToken,
414-
"Content-type": "application/x-www-form-urlencoded",
415-
"User-Agent": "okhttp/3.10.0",
442+
var res oauth2.Token
443+
var err error
444+
var uri string
445+
var headers map[string]string
446+
var data url.Values
447+
switch v.config.Brand {
448+
case "hyundai":
449+
uri = v.config.URI + TokenURL
450+
headers = map[string]string{
451+
"Authorization": "Basic " + v.config.BasicToken,
452+
"Content-type": "application/x-www-form-urlencoded",
453+
// "User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19_CCS_APP_AOS",
454+
"User-Agent": "okhttp/3.10.0",
455+
}
456+
457+
data = url.Values{
458+
"grant_type": {"refresh_token"},
459+
"redirect_uri": {"https://www.getpostman.com/oauth2/callback"},
460+
"refresh_token": {token.RefreshToken},
461+
}
462+
463+
case "kia":
464+
uri = v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
465+
headers = map[string]string{
466+
"Content-type": "application/x-www-form-urlencoded",
467+
"User-Agent": "Mozilla/5.0 (Linux; Android 4.1.1; Galaxy Nexus Build/JRO03C) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19_CCS_APP_AOS",
468+
}
469+
data = url.Values{
470+
"grant_type": {"refresh_token"},
471+
"refresh_token": {token.RefreshToken},
472+
"client_id": {v.config.CCSPServiceID},
473+
"client_secret": {"secret"},
474+
}
475+
default:
476+
err = errors.New("Unsupported brand")
416477
}
417478

418-
data := url.Values{
419-
"grant_type": {"refresh_token"},
420-
"redirect_uri": {"https://www.getpostman.com/oauth2/callback"},
421-
"refresh_token": {token.RefreshToken},
479+
// request token only if we didn't run unto the default branch
480+
if err != nil {
481+
return nil, err
422482
}
423483

424-
req, err := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers)
484+
req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers)
485+
if err != nil {
486+
return nil, err
487+
}
425488

426-
var res oauth2.Token
427-
if err == nil {
428-
err = v.DoJSON(req, &res)
489+
err = v.DoJSON(req, &res)
490+
// carry over the old refresh token (if any and not populated already)
491+
if res.RefreshToken == "" && token.RefreshToken != "" {
492+
res.RefreshToken = token.RefreshToken
429493
}
430494

431495
return util.TokenWithExpiry(&res), err
@@ -438,13 +502,15 @@ func (v *Identity) Login(user, password, language, brand string) (err error) {
438502
var code string
439503
switch brand {
440504
case "kia":
441-
code, err = v.brandLoginKiaEU(user, password)
442-
if err == nil {
443-
var token *oauth2.Token
444-
if token, err = v.exchangeCodeKiaEU(code); err == nil {
445-
v.TokenSource = oauth.RefreshTokenSource(token, v)
446-
}
505+
// the "password" now is the refresh token ...
506+
// code, err = v.brandLoginKiaEU(user, password)
507+
// if err == nil {
508+
var token *oauth2.Token
509+
if token, err = v.exchangeCodeKiaEURefreshToken(password); err == nil {
510+
v.TokenSource = oauth.RefreshTokenSource(token, v)
447511
}
512+
v.deviceID, err = v.getDeviceID()
513+
// }
448514
case "hyundai":
449515
v.deviceID, err = v.getDeviceID()
450516

0 commit comments

Comments
 (0)