Hyundai/Kia: fix token refresh (#24580)

This commit is contained in:
stefan 2025-10-20 14:25:55 +02:00 • committed by GitHub
parent aa82947700
commit 3fd38808a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 100 deletions

View file

@ -6,11 +6,11 @@ products:
requirements:
description:
en: |
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/Hyundai-Kia:-Refresh%E2%80%90Token-ermitteln#english-version)).
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/Hyundai-Kia:-Refresh-Token)).
Some models (e.g. Kona) 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.
de: |
Anstelle des Passworts muss in das Passwort-Feld ein `refresh_token` eingetragen werden ([Anleitung](https://github.com/evcc-io/evcc/wiki/Hyundai-Kia:-Refresh%E2%80%90Token-ermitteln)).
Anstelle des Passworts muss in das Passwort-Feld ein `refresh_token` eingetragen werden ([Anleitung](https://github.com/evcc-io/evcc/wiki/Hyundai-Kia:-Refresh-Token)).
Manche Modelle (z.B. Kona) 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.
params:

View file

@ -6,11 +6,11 @@ products:
requirements:
description:
en: |
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/Hyundai-Kia:-Refresh%E2%80%90Token-ermitteln#english-version)).
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/Hyundai-Kia:-Refresh-Token)).
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.
de: |
Anstelle des Passworts muss in das Passwort-Feld ein `refresh_token` eingetragen werden ([Anleitung](https://github.com/evcc-io/evcc/wiki/Hyundai-Kia:-Refresh%E2%80%90Token-ermitteln)).
Anstelle des Passworts muss in das Passwort-Feld ein `refresh_token` eingetragen werden ([Anleitung](https://github.com/evcc-io/evcc/wiki/Hyundai-Kia:-Refresh-Token)).
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.
params:

View file

@ -26,15 +26,14 @@ func init() {
func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
settings := bluelink.Config{
URI: "https://prd.eu-ccapi.hyundai.com:8080",
BasicToken: "KUy49XxPzLpLuoK0xhBC77W6VXhmtQR9iQhmIFjjoY4IpxsV",
CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
CCSPApplicationID: bluelink.HyundaiAppID,
AuthClientID: "64621b96-0f0d-11ec-82a8-0242ac130003",
PushType: "GCM",
CCSPServiceSecret: "KUy49XxPzLpLuoK0xhBC77W6VXhmtQR9iQhmIFjjoY4IpxsV",
CCSPApplicationID: "014d2225-8495-4735-812d-2616334fd15d",
Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=",
Brand: "hyundai",
BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==",
PushType: "GCM",
LoginFormHost: "https://idpconnect-eu.hyundai.com",
BrandAuthUrl: "%s/auth/api/v2/user/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect&lang=%s&state=ccsp",
Brand: "hyundai",
}
return newBluelinkFromConfig("hyundai", other, settings)
@ -44,14 +43,13 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) {
settings := bluelink.Config{
URI: "https://prd.eu-ccapi.kia.com:8080",
BasicToken: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==",
CCSPServiceID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a",
CCSPApplicationID: bluelink.KiaAppID,
AuthClientID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a",
BrandAuthUrl: "%s/auth/api/v2/user/oauth2/authorize?response_type=code&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect&lang=%s&state=ccsp",
PushType: "APNS",
CCSPServiceSecret: "secret",
CCSPApplicationID: "a2b8469b-30a3-4361-8e13-6fceea8fbe74",
Cfb: "wLTVxwidmH8CfJYBWSnHD6E0huk0ozdiuygB4hLkM5XCgzAL1Dk5sE36d/bx5PFMbZs=",
BasicToken: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==",
LoginFormHost: "https://idpconnect-eu.kia.com",
PushType: "APNS",
Brand: "kia",
}

View file

@ -25,17 +25,16 @@ const (
SilentSigninURL = "/api/v1/user/silentsignin"
LanguageURL = "/api/v1/user/language"
LoginURL = "/api/v1/user/signin"
TokenURL = "/api/v1/user/oauth2/token"
TokenURL = "/auth/api/v2/user/oauth2/token"
)
// Config is the bluelink API configuration
type Config struct {
URI string
AuthClientID string // v2
BrandAuthUrl string // v2
BasicToken string
CCSPServiceID string
CCSPApplicationID string
CCSPServiceSecret string
PushType string
Cfb string
LoginFormHost string
@ -103,71 +102,20 @@ func (v *Identity) getDeviceID() (string, error) {
return res.ResMsg.DeviceID, err
}
func (v *Identity) exchangeCodeKiaEURefreshToken(accCode string) (*oauth2.Token, error) {
uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
// RefreshToken implements oauth.TokenRefresher
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
var res oauth2.Token
uri := v.config.LoginFormHost + TokenURL
headers := map[string]string{
"Content-type": "application/x-www-form-urlencoded",
"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",
}
data := url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {accCode},
"refresh_token": {token.RefreshToken},
"client_id": {v.config.CCSPServiceID},
"client_secret": {"secret"},
}
var token oauth2.Token
req, _ := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers)
err := v.DoJSON(req, &token)
// manually set the refresh token
token.RefreshToken = accCode
return util.TokenWithExpiry(&token), err
}
// RefreshToken implements oauth.TokenRefresher
func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
var res oauth2.Token
var err error
var uri string
var headers map[string]string
var data url.Values
switch v.config.Brand {
case "hyundai":
uri = v.config.URI + TokenURL
headers = map[string]string{
"Authorization": "Basic " + v.config.BasicToken,
"Content-type": "application/x-www-form-urlencoded",
"User-Agent": "okhttp/3.10.0",
}
data = url.Values{
"grant_type": {"refresh_token"},
"redirect_uri": {"https://www.getpostman.com/oauth2/callback"},
"refresh_token": {token.RefreshToken},
}
case "kia":
uri = v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token"
headers = map[string]string{
"Content-type": "application/x-www-form-urlencoded",
"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",
}
data = url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {token.RefreshToken},
"client_id": {v.config.CCSPServiceID},
"client_secret": {"secret"},
}
default:
err = errors.New("unsupported brand")
}
// request token only if we didn't run unto the default branch
if err != nil {
return nil, err
"client_secret": {v.config.CCSPServiceSecret},
}
req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers)
@ -188,31 +136,23 @@ func (v *Identity) Login(user, password, language, brand string) (err error) {
if user == "" || password == "" {
return api.ErrMissingCredentials
}
// var code string
switch brand {
case "kia":
// the "password" now is the refresh token ...
var token *oauth2.Token
token, err = v.exchangeCodeKiaEURefreshToken(password)
if err == nil {
v.TokenSource = oauth.RefreshTokenSource(token, v)
v.deviceID, err = v.getDeviceID()
}
case "hyundai":
var token *oauth2.Token
token, err = v.exchangeCodeKiaEURefreshToken(password)
if err == nil {
v.TokenSource = oauth.RefreshTokenSource(token, v)
v.deviceID, err = v.getDeviceID()
}
default:
err = fmt.Errorf("unknown brand (%s)", brand)
return fmt.Errorf("unknown brand (%s)", brand)
}
token, err := v.RefreshToken(&oauth2.Token{RefreshToken: password})
if err != nil {
err = fmt.Errorf("login failed: %w", err)
return fmt.Errorf("login failed: %w", err)
}
v.TokenSource = oauth.RefreshTokenSource(token, v)
v.deviceID, err = v.getDeviceID()
if err != nil {
return fmt.Errorf("error getting device id: %w", err)
}
return err

View file

@ -1,6 +0,0 @@
package bluelink
const (
KiaAppID = "a2b8469b-30a3-4361-8e13-6fceea8fbe74"
HyundaiAppID = "014d2225-8495-4735-812d-2616334fd15d"
)