Some checks failed
Release / Validate Tag (push) Has been cancelled
Release / call-build-workflow (push) Has been cancelled
Release / Publish Docker :release (push) Has been cancelled
Release / Github & APT (push) Has been cancelled
Release / Demo (push) Has been cancelled
Release / Hassio Addon (push) Has been cancelled
Release / Delete Release Branch (push) Has been cancelled
192 lines
6.3 KiB
Go
192 lines
6.3 KiB
Go
package vehicle
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/evcc-io/evcc/vehicle/bluelink"
|
|
)
|
|
|
|
// https://github.com/Hacksore/bluelinky
|
|
// https://github.com/Hyundai-Kia-Connect/hyundai_kia_connect_api/pull/353/files
|
|
|
|
// Bluelink is an api.Vehicle implementation
|
|
type Bluelink struct {
|
|
*embed
|
|
*bluelink.Provider
|
|
}
|
|
|
|
func init() {
|
|
registry.Add("kia", NewKiaFromConfig)
|
|
registry.Add("hyundai", NewHyundaiFromConfig)
|
|
registry.Add("genesis", NewGenesisFromConfig)
|
|
}
|
|
|
|
// NewHyundaiFromConfig creates a new vehicle
|
|
func NewHyundaiFromConfig(other map[string]any) (api.Vehicle, error) {
|
|
// Decode region early so we can select the right config
|
|
cc := struct {
|
|
Region string
|
|
}{
|
|
Region: "Europe",
|
|
}
|
|
if region, ok := other["region"].(string); ok {
|
|
cc.Region = strings.TrimSpace(region)
|
|
}
|
|
|
|
var settings bluelink.Config
|
|
switch strings.ToLower(cc.Region) {
|
|
case "europe", "":
|
|
settings = bluelink.Config{
|
|
URI: "https://prd.eu-ccapi.hyundai.com:8080",
|
|
CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654",
|
|
CCSPServiceSecret: "KUy49XxPzLpLuoK0xhBC77W6VXhmtQR9iQhmIFjjoY4IpxsV",
|
|
CCSPApplicationID: "014d2225-8495-4735-812d-2616334fd15d",
|
|
Cfb: "RFtoRq/vDXJmRndoZaZQyfOot7OrIqGVFj96iY2WL3yyH5Z/pUvlUhqmCxD2t+D65SQ=",
|
|
BasicToken: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==",
|
|
PushType: "GCM",
|
|
LoginFormHost: "https://idpconnect-eu.hyundai.com",
|
|
Brand: "hyundai",
|
|
// OneApp/CCI login (bypasses the IDPConnect WAF block on the legacy
|
|
// authorize endpoint, see vehicle/bluelink/cci.go)
|
|
CCI: &bluelink.CCIConfig{
|
|
OneAppClientID: "4f4953b5-02e1-4dbc-8599-87e983ee1be5",
|
|
OneAppRedirectURI: "https://oneapp.hyundai.com/redirect",
|
|
APIURL: "https://cci-api-eu.hyundai.com",
|
|
PackageID: "com.hyundai.oneapp.eu",
|
|
ClientName: "hyundai",
|
|
OSVersion: "18.7",
|
|
NotificationProvider: "APNS",
|
|
},
|
|
}
|
|
case "australia", "new zealand":
|
|
settings = bluelink.Config{
|
|
URI: bluelink.HyundaiAUBaseURI,
|
|
CCSPServiceID: bluelink.HyundaiAUCCSPServiceID,
|
|
CCSPServiceSecret: "e6fbwHM32YNbhQl0pviaPp3rf4t3S6k91eceA3MJLdbdThCO",
|
|
CCSPApplicationID: bluelink.HyundaiAUCCSPAppID,
|
|
Cfb: bluelink.HyundaiAUCfb,
|
|
BasicToken: bluelink.HyundaiAUBasicToken,
|
|
PushType: "GCM",
|
|
LoginFormHost: bluelink.HyundaiAUBaseURI,
|
|
TokenURL: "/api/v1/user/oauth2/token",
|
|
Brand: "hyundai",
|
|
UseBasicAuth: true,
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unknown region: %s", cc.Region)
|
|
}
|
|
|
|
return newBluelinkFromConfig("hyundai", other, settings)
|
|
}
|
|
|
|
// NewKiaFromConfig creates a new vehicle
|
|
func NewKiaFromConfig(other map[string]any) (api.Vehicle, error) {
|
|
settings := bluelink.Config{
|
|
URI: "https://prd.eu-ccapi.kia.com:8080",
|
|
CCSPServiceID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a",
|
|
CCSPServiceSecret: "secret",
|
|
CCSPApplicationID: "a2b8469b-30a3-4361-8e13-6fceea8fbe74",
|
|
Cfb: "wLTVxwidmH8CfJYBWSnHD6E0huk0ozdiuygB4hLkM5XCgzAL1Dk5sE36d/bx5PFMbZs=",
|
|
BasicToken: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==",
|
|
LoginFormHost: "https://idpconnect-eu.kia.com",
|
|
PushType: "APNS",
|
|
Brand: "kia",
|
|
// OneApp/CCI login (bypasses the IDPConnect WAF block on the legacy
|
|
// authorize endpoint, see vehicle/bluelink/cci.go)
|
|
CCI: &bluelink.CCIConfig{
|
|
OneAppClientID: "01b36c86-79e8-486c-8009-15f2ad88d670",
|
|
OneAppRedirectURI: "https://oneapp.kia.com/redirect",
|
|
APIURL: "https://cci-api-eu.kia.com",
|
|
PackageID: "com.kia.oneapp.eu",
|
|
ClientName: "kia",
|
|
OSVersion: "27",
|
|
NotificationProvider: "IOS_APPSTORE",
|
|
},
|
|
}
|
|
|
|
return newBluelinkFromConfig("kia", other, settings)
|
|
}
|
|
|
|
// NewGenesisFromConfig creates a new vehicle
|
|
// Constants sourced from hyundai_kia_connect_api (KiaUvoApiEU.py, BRAND_GENESIS, Europe)
|
|
func NewGenesisFromConfig(other map[string]any) (api.Vehicle, error) {
|
|
settings := bluelink.Config{
|
|
URI: "https://prd-eu-ccapi.genesis.com:443",
|
|
CCSPServiceID: "3020afa2-30ff-412a-aa51-d28fbe901e10",
|
|
CCSPServiceSecret: "FKDdlef2ffdleFEweELFKERiLER2FED21sDdwdgQz6hFESE3",
|
|
CCSPApplicationID: "f11f2b86-e0e7-4851-90df-5600b01d8b70",
|
|
Cfb: "RFtoRq/vDXJmRndoZaZQyYo3/qFLtVReW8P7utRPcc0ZxOzOELm9mexvviBk/qqIp4A=",
|
|
BasicToken: "MzAyMGFmYTItMzBmZi00MTJhLWFhNTEtZDI4ZmJlOTAxZTEwOkZLRGRsZWYyZmZkbGVGRXdlRUxGS0VSaUxFUjJGRUQyMXNEZHdkZ1F6NmhGRVNFMw==",
|
|
PushType: "GCM",
|
|
LoginFormHost: "https://idpconnect-eu.genesis.com",
|
|
Brand: "genesis",
|
|
}
|
|
|
|
return newBluelinkFromConfig("genesis", other, settings)
|
|
}
|
|
|
|
// newBluelinkFromConfig creates a new Vehicle
|
|
func newBluelinkFromConfig(brand string, other map[string]any, settings bluelink.Config) (api.Vehicle, error) {
|
|
cc := struct {
|
|
embed `mapstructure:",squash"`
|
|
User, Password string
|
|
VIN string
|
|
Language string
|
|
Region string
|
|
Expiry time.Duration
|
|
Cache time.Duration
|
|
}{
|
|
Language: "en",
|
|
Expiry: expiry,
|
|
Cache: interval,
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log := util.NewLogger(brand).Redact(cc.User, cc.Password, cc.VIN)
|
|
identity := bluelink.NewIdentity(log, settings)
|
|
|
|
if err := identity.Login(cc.User, cc.Password, cc.Language, brand); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
api := bluelink.NewAPI(log, settings.URI, identity.Request)
|
|
|
|
vehicle, err := ensureVehicleEx(
|
|
cc.VIN, api.Vehicles,
|
|
func(v bluelink.Vehicle) (string, error) {
|
|
return v.VIN, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := &Bluelink{
|
|
embed: &cc.embed,
|
|
Provider: bluelink.NewProvider(api, vehicle, cc.Cache),
|
|
}
|
|
|
|
// Try to fetch battery capacity from vehicle if the user didn't provide one in the configuration
|
|
var capacity float64 = 0
|
|
if v.Capacity_ == 0 {
|
|
capacity = fetchVehicleCapacity(api, vehicle, capacity)
|
|
}
|
|
|
|
v.fromVehicle(vehicle.VehicleName, capacity)
|
|
|
|
return v, nil
|
|
}
|
|
|
|
func fetchVehicleCapacity(api *bluelink.API, vehicle bluelink.Vehicle, capacity float64) float64 {
|
|
if res, err := api.StatusLatest(vehicle); err == nil {
|
|
capacity, _ = res.Capacity()
|
|
}
|
|
return capacity
|
|
}
|