evcc-io/vehicle/bluelink.go
2026-05-23 21:49:08 +02:00

137 lines
4.4 KiB
Go

package vehicle
import (
"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) {
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",
}
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",
}
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
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
}