diff --git a/templates/definition/vehicle/kia.yaml b/templates/definition/vehicle/kia.yaml index f4ea06107..d56ed1f1c 100644 --- a/templates/definition/vehicle/kia.yaml +++ b/templates/definition/vehicle/kia.yaml @@ -5,8 +5,14 @@ products: generic: Bluelink requirements: description: - 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. - 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. + 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/Kia:-Refresh%E2%80%90Token-ermitteln#english-version)). + + 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/Kia:-Refresh%E2%80%90Token-ermitteln)). + + 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: - preset: vehicle-base - preset: vehicle-language diff --git a/vehicle/bluelink.go b/vehicle/bluelink.go index e5488fcae..4ed3c2fed 100644 --- a/vehicle/bluelink.go +++ b/vehicle/bluelink.go @@ -33,6 +33,7 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) { 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", PushType: "GCM", Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=", + Brand: "hyundai", // for oauth2?? // LoginFormHost: "https://idpconnect-eu.hyundai.com", // AuthClientID: "6d477c38-3ca4-4cf3-9557-2a1929a94654", @@ -54,6 +55,7 @@ func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) { PushType: "APNS", Cfb: "wLTVxwidmH8CfJYBWSnHD6E0huk0ozdiuygB4hLkM5XCgzAL1Dk5sE36d/bx5PFMbZs=", LoginFormHost: "https://idpconnect-eu.kia.com", + Brand: "kia", } return newBluelinkFromConfig("kia", other, settings) diff --git a/vehicle/bluelink/identity.go b/vehicle/bluelink/identity.go index 973710884..51e826ddc 100644 --- a/vehicle/bluelink/identity.go +++ b/vehicle/bluelink/identity.go @@ -42,6 +42,7 @@ type Config struct { PushType string Cfb string LoginFormHost string + Brand string } // Identity implements the Kia/Hyundai bluelink identity. @@ -247,6 +248,7 @@ func (v *Identity) brandLoginHyundaiEU(cookieClient *request.Helper, user, passw return code, err } +/* Unused for now func (v *Identity) brandLoginKiaEU(user, password string) (string, error) { cookieClient := request.NewHelper(v.log) cookieClient.Client.Jar, _ = cookiejar.New(&cookiejar.Options{ @@ -255,7 +257,8 @@ func (v *Identity) brandLoginKiaEU(user, password string) (string, error) { 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", + "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", + // "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", } data := url.Values{ @@ -334,6 +337,7 @@ func (v *Identity) brandLoginKiaEU(user, password string) (string, error) { return code, nil } +*/ func (v *Identity) bluelinkLogin(cookieClient *request.Helper, user, password string) (string, error) { data := map[string]interface{}{ @@ -385,6 +389,31 @@ func (v *Identity) exchangeCodeHyundaiEU(accCode string) (*oauth2.Token, error) return util.TokenWithExpiry(&token), err } +func (v *Identity) exchangeCodeKiaEURefreshToken(accCode string) (*oauth2.Token, error) { + 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": {accCode}, + "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 +} + +/* Unused for now func (v *Identity) exchangeCodeKiaEU(accCode string) (*oauth2.Token, error) { uri := v.config.LoginFormHost + "/auth/api/v2/user/oauth2/token" headers := map[string]string{ @@ -406,26 +435,61 @@ func (v *Identity) exchangeCodeKiaEU(accCode string) (*oauth2.Token, error) { return util.TokenWithExpiry(&token), err } +*/ // RefreshToken implements oauth.TokenRefresher func (v *Identity) RefreshToken(token *oauth2.Token) (*oauth2.Token, error) { - 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}, - } - - req, err := request.New(http.MethodPost, v.config.URI+TokenURL, strings.NewReader(data.Encode()), headers) - var res oauth2.Token - if err == nil { - err = v.DoJSON(req, &res) + 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": "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", + "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 + } + + req, err := request.New(http.MethodPost, uri, strings.NewReader(data.Encode()), headers) + if err != nil { + return nil, err + } + + err = v.DoJSON(req, &res) + // carry over the old refresh token (if any and not populated already) + if res.RefreshToken == "" && token.RefreshToken != "" { + res.RefreshToken = token.RefreshToken } return util.TokenWithExpiry(&res), err @@ -438,13 +502,15 @@ func (v *Identity) Login(user, password, language, brand string) (err error) { var code string switch brand { case "kia": - code, err = v.brandLoginKiaEU(user, password) - if err == nil { - var token *oauth2.Token - if token, err = v.exchangeCodeKiaEU(code); err == nil { - v.TokenSource = oauth.RefreshTokenSource(token, v) - } + // the "password" now is the refresh token ... + // code, err = v.brandLoginKiaEU(user, password) + // if err == nil { + var token *oauth2.Token + if token, err = v.exchangeCodeKiaEURefreshToken(password); err == nil { + v.TokenSource = oauth.RefreshTokenSource(token, v) } + v.deviceID, err = v.getDeviceID() + // } case "hyundai": v.deviceID, err = v.getDeviceID()