From a24caac73d9ada57e34e4538a9921a80d8cf190b Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 13 Sep 2020 16:06:33 +0200 Subject: [PATCH] Add support for Hyundai api (#329) --- core/wrapper/socestimator.go | 2 +- vehicle/bluelink/bluelink.go | 392 +++++++++++++++++++++++++++++++++++ vehicle/hyundai.go | 61 ++++++ vehicle/kia.go | 389 +++------------------------------- 4 files changed, 482 insertions(+), 362 deletions(-) create mode 100644 vehicle/bluelink/bluelink.go create mode 100644 vehicle/hyundai.go diff --git a/core/wrapper/socestimator.go b/core/wrapper/socestimator.go index 576cc0953..05a8da58c 100644 --- a/core/wrapper/socestimator.go +++ b/core/wrapper/socestimator.go @@ -82,7 +82,7 @@ func (s *SocEstimator) SoC(chargedEnergy float64) (float64, error) { s.prevChargedEnergy = math.Max(chargedEnergy, 0) s.prevSoC = s.socCharge } else { - s.socCharge = math.Min(f + energyDelta / s.energyPerSocStep, 100) + s.socCharge = math.Min(f+energyDelta/s.energyPerSocStep, 100) s.log.TRACE.Printf("soc estimated: %.2f%% (vehicle: %.2f%%)", s.socCharge, f) } } diff --git a/vehicle/bluelink/bluelink.go b/vehicle/bluelink/bluelink.go new file mode 100644 index 000000000..d52b2f888 --- /dev/null +++ b/vehicle/bluelink/bluelink.go @@ -0,0 +1,392 @@ +package bluelink + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/cookiejar" + "net/url" + "strings" + "time" + + "github.com/andig/evcc/provider" + "github.com/andig/evcc/util" + "github.com/google/uuid" + "golang.org/x/net/publicsuffix" +) + +const ( + resOK = "S" + resAuthFail = "F" +) + +var errAuthFail = errors.New("authorization failed") + +// Config is the bluelink API configuration +type Config struct { + URI string + TokenAuth string + CCSPServiceID string + CCSPApplicationID string + DeviceID string + Lang string + Login string + AccessToken string + Vehicles string + SendPIN string + GetStatus string +} + +// API is an api.Vehicle implementation with configurable getters and setters. +type API struct { + *util.HTTPHelper + user string + password string + pin string + chargeG func() (float64, error) + config Config + auth Auth +} + +// Auth bundles miscellaneous authorization data +type Auth struct { + deviceID string + vehicleID string + controlToken string +} + +type response struct { + RetCode string `json:"retCode"` + ResMsg struct { + DeviceID string `json:"deviceId"` + EvStatus struct { + BatteryStatus float64 `json:"batteryStatus"` + } `json:"evStatus"` + Vehicles []struct { + VehicleID string `json:"vehicleId"` + } `json:"vehicles"` + } `json:"resMsg"` +} + +// New creates a new BlueLink API +func New(log *util.Logger, + user, password, pin string, cache time.Duration, + config Config, +) (*API, error) { + v := &API{ + HTTPHelper: util.NewHTTPHelper(log), + config: config, + user: user, + password: password, + pin: pin, + } + + // api is unbelievably slow when retrieving status + v.HTTPHelper.Client.Timeout = 120 * time.Second + + v.chargeG = provider.NewCached(v.chargeState, cache).FloatGetter() + + return v, nil +} + +// request builds an HTTP request with headers and body +func (v *API) request(method, uri string, headers map[string]string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequest(method, uri, body) + if err != nil { + return req, err + } + + for k, v := range headers { + req.Header.Add(k, v) + } + + return req, nil +} + +// jsonRequest builds an HTTP json request with headers and body +func (v *API) jsonRequest(method, uri string, headers map[string]string, data interface{}) (*http.Request, error) { + body, err := json.Marshal(data) + if err != nil { + return nil, err + } + + return v.request(method, uri, headers, bytes.NewReader(body)) +} + +// Credits to https://openwb.de/forum/viewtopic.php?f=5&t=1215&start=10#p11877 + +func (v *API) getDeviceID() (string, error) { + uniID, _ := uuid.NewUUID() + data := map[string]interface{}{ + "pushRegId": "1", + "pushType": "GCM", + "uuid": uniID.String(), + } + + headers := map[string]string{ + "ccsp-service-id": v.config.CCSPServiceID, + "Content-type": "application/json;charset=UTF-8", + "User-Agent": "okhttp/3.10.0", + } + + var resp response + req, err := v.jsonRequest(http.MethodPost, v.config.URI+v.config.DeviceID, headers, data) + if err == nil { + _, err = v.RequestJSON(req, &resp) + } + + return resp.ResMsg.DeviceID, err +} + +func (v *API) getCookies() (cookieClient *util.HTTPHelper, err error) { + cookieClient = util.NewHTTPHelper(v.Log) + cookieClient.Client.Jar, err = cookiejar.New(&cookiejar.Options{ + PublicSuffixList: publicsuffix.List, + }) + + if err == nil { + uri := fmt.Sprintf( + "%s/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=%s&redirect_uri=%s/api/v1/user/oauth2/redirect", + v.config.URI, + v.config.CCSPServiceID, + v.config.URI, + ) + _, err = cookieClient.Get(uri) + } + + return cookieClient, err +} + +func (v *API) setLanguage(cookieClient *util.HTTPHelper) error { + headers := map[string]string{ + "Content-type": "application/json", + } + + data := map[string]interface{}{ + "lang": "en", + } + + req, err := v.jsonRequest(http.MethodPost, v.config.URI+v.config.Lang, headers, data) + if err == nil { + _, err = cookieClient.Request(req) + } + + return err +} + +func (v *API) login(cookieClient *util.HTTPHelper) (string, error) { + headers := map[string]string{ + "Content-type": "application/json", + } + + data := map[string]interface{}{ + "email": v.user, + "password": v.password, + } + + req, err := v.jsonRequest(http.MethodPost, v.config.URI+v.config.Login, headers, data) + if err != nil { + return "", err + } + + var redirect struct { + RedirectURL string `json:"redirectUrl"` + } + + var accCode string + if _, err = cookieClient.RequestJSON(req, &redirect); err == nil { + if parsed, err := url.Parse(redirect.RedirectURL); err == nil { + accCode = parsed.Query().Get("code") + } + } + + return accCode, err +} + +func (v *API) getToken(accCode string) (string, error) { + headers := map[string]string{ + "Authorization": "Basic " + v.config.TokenAuth, + "Content-type": "application/x-www-form-urlencoded", + "User-Agent": "okhttp/3.10.0", + } + + redirectURL := v.config.URI + "/api/v1/user/oauth2/redirect" + data := fmt.Sprintf("grant_type=authorization_code&redirect_uri=%s&code=%s", url.PathEscape(redirectURL), accCode) + + req, err := v.request(http.MethodPost, v.config.URI+v.config.AccessToken, headers, strings.NewReader(data)) + if err != nil { + return "", err + } + + var tokens struct { + TokenType string `json:"token_type"` + AccessToken string `json:"access_token"` + } + + var accToken string + if _, err = v.RequestJSON(req, &tokens); err == nil { + accToken = fmt.Sprintf("%s %s", tokens.TokenType, tokens.AccessToken) + } + + return accToken, err +} + +func (v *API) getVehicles(accToken, did string) (string, error) { + headers := map[string]string{ + "Authorization": accToken, + "ccsp-device-id": did, + "ccsp-application-id": v.config.CCSPApplicationID, + "offset": "1", + "User-Agent": "okhttp/3.10.0", + } + + req, err := v.request(http.MethodGet, v.config.URI+v.config.Vehicles, headers, nil) + if err == nil { + var resp response + if _, err = v.RequestJSON(req, &resp); err == nil { + if len(resp.ResMsg.Vehicles) == 1 { + return resp.ResMsg.Vehicles[0].VehicleID, nil + } + + err = errors.New("couldn't find vehicle") + } + } + + return "", err +} + +func (v *API) preWakeup(accToken, did, vid string) error { + data := map[string]interface{}{ + "action": "prewakeup", + "deviceId": did, + } + + headers := map[string]string{ + "Authorization": accToken, + "ccsp-device-id": did, + "ccsp-application-id": v.config.CCSPApplicationID, + "offset": "1", + "Content-Type": "application/json;charset=UTF-8", + "User-Agent": "okhttp/3.10.0", + } + + uri := v.config.URI + v.config.Vehicles + "/" + vid + "/control/engine" + req, err := v.jsonRequest(http.MethodPost, uri, headers, data) + if err == nil { + _, err = v.Request(req) + } + + return err +} + +func (v *API) sendPIN(deviceID, accToken string) (string, error) { + data := map[string]interface{}{ + "deviceId": deviceID, + "pin": string(v.pin), + } + + headers := map[string]string{ + "Authorization": accToken, + "Content-type": "application/json;charset=UTF-8", + "User-Agent": "okhttp/3.10.0", + } + + var token struct { + ControlToken string `json:"controlToken"` + } + + req, err := v.jsonRequest(http.MethodPut, v.config.URI+v.config.SendPIN, headers, data) + if err == nil { + _, err = v.RequestJSON(req, &token) + } + + controlToken := "" + if err == nil { + controlToken = "Bearer " + token.ControlToken + + } + + return controlToken, err +} + +func (v *API) authFlow() (err error) { + v.auth.deviceID, err = v.getDeviceID() + + var cookieClient *util.HTTPHelper + if err == nil { + cookieClient, err = v.getCookies() + } + + if err == nil { + err = v.setLanguage(cookieClient) + } + + var kiaAccCode string + if err == nil { + kiaAccCode, err = v.login(cookieClient) + } + + var kiaAccToken string + if err == nil { + kiaAccToken, err = v.getToken(kiaAccCode) + } + + if err == nil { + v.auth.vehicleID, err = v.getVehicles(kiaAccToken, v.auth.deviceID) + } + + if err == nil { + err = v.preWakeup(kiaAccToken, v.auth.deviceID, v.auth.vehicleID) + } + + if err == nil { + v.auth.controlToken, err = v.sendPIN(v.auth.deviceID, kiaAccToken) + } + + return err +} + +func (v *API) getStatus() (float64, error) { + headers := map[string]string{ + "Authorization": v.auth.controlToken, + "ccsp-device-id": v.auth.deviceID, + "Content-Type": "application/json", + } + + var resp response + uri := v.config.URI + v.config.GetStatus + "/" + v.auth.vehicleID + "/status" + req, err := v.request(http.MethodGet, uri, headers, nil) + if err == nil { + _, err = v.RequestJSON(req, &resp) + + if err == nil && resp.RetCode != resOK { + err = errors.New("unexpected response") + if resp.RetCode == resAuthFail { + err = errAuthFail + } + } + } + + return resp.ResMsg.EvStatus.BatteryStatus, err +} + +// chargeState implements the Vehicle.ChargeState interface +func (v *API) chargeState() (float64, error) { + soc, err := v.getStatus() + + if err != nil && errors.Is(err, errAuthFail) { + if err = v.authFlow(); err == nil { + soc, err = v.getStatus() + } + } + + return soc, err +} + +// ChargeState implements the Vehicle.ChargeState interface +func (v *API) ChargeState() (float64, error) { + return v.chargeG() +} diff --git a/vehicle/hyundai.go b/vehicle/hyundai.go new file mode 100644 index 000000000..3ef427e43 --- /dev/null +++ b/vehicle/hyundai.go @@ -0,0 +1,61 @@ +package vehicle + +import ( + "time" + + "github.com/andig/evcc/api" + "github.com/andig/evcc/util" + "github.com/andig/evcc/vehicle/bluelink" +) + +// Hyundai is an api.Vehicle implementation +type Hyundai struct { + *embed + *bluelink.API +} + +func init() { + registry.Add("hyundai", NewHyundaiFromConfig) +} + +// NewHyundaiFromConfig creates a new Vehicle +func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) { + cc := struct { + Title string + Capacity int64 + User, Password string + PIN string + Cache time.Duration + }{} + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + settings := bluelink.Config{ + URI: "https://prd.eu-ccapi.hyundai.com:8080", + TokenAuth: "NmQ0NzdjMzgtM2NhNC00Y2YzLTk1NTctMmExOTI5YTk0NjU0OktVeTQ5WHhQekxwTHVvSzB4aEJDNzdXNlZYaG10UVI5aVFobUlGampvWTRJcHhzVg==", + CCSPServiceID: "6d477c38-3ca4-4cf3-9557-2a1929a94654", + CCSPApplicationID: "99cfff84-f4e2-4be8-a5ed-e5b755eb6581", + DeviceID: "/api/v1/spa/notifications/register", + Lang: "/api/v1/user/language", + Login: "/api/v1/user/signin", + AccessToken: "/api/v1/user/oauth2/token", + Vehicles: "/api/v1/spa/vehicles", + SendPIN: "/api/v1/user/pin", + GetStatus: "/api/v2/spa/vehicles/", + } + + log := util.NewLogger("hyundai") + api, err := bluelink.New(log, cc.User, cc.Password, cc.PIN, cc.Cache, settings) + if err != nil { + return nil, err + } + + v := &Hyundai{ + embed: &embed{cc.Title, cc.Capacity}, + API: api, + } + + return v, nil +} diff --git a/vehicle/kia.go b/vehicle/kia.go index b743ebece..e82ee5e73 100644 --- a/vehicle/kia.go +++ b/vehicle/kia.go @@ -1,65 +1,17 @@ package vehicle import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/http/cookiejar" - "net/url" - "strings" "time" "github.com/andig/evcc/api" - "github.com/andig/evcc/provider" "github.com/andig/evcc/util" - "github.com/google/uuid" - "golang.org/x/net/publicsuffix" + "github.com/andig/evcc/vehicle/bluelink" ) -const ( - resOK = "S" - kiaURLDeviceID = "https://prd.eu-ccapi.kia.com:8080/api/v1/spa/notifications/register" - kiaURLCookies = "https://prd.eu-ccapi.kia.com:8080/api/v1/user/oauth2/authorize?response_type=code&state=test&client_id=fdc85c00-0a2f-4c64-bcb4-2cfb1500730a&redirect_uri=https://prd.eu-ccapi.kia.com:8080/api/v1/user/oauth2/redirect" - kiaURLLang = "https://prd.eu-ccapi.kia.com:8080/api/v1/user/language" - kiaURLLogin = "https://prd.eu-ccapi.kia.com:8080/api/v1/user/signin" - kiaURLAccessToken = "https://prd.eu-ccapi.kia.com:8080/api/v1/user/oauth2/token" - kiaURLVehicles = "https://prd.eu-ccapi.kia.com:8080/api/v1/spa/vehicles" - kiaURLPreWakeup = "https://prd.eu-ccapi.kia.com:8080/api/v1/spa/vehicles/" - kiaURLSendPIN = "https://prd.eu-ccapi.kia.com:8080/api/v1/user/pin" - kiaURLGetStatus = "https://prd.eu-ccapi.kia.com:8080/api/v2/spa/vehicles/" -) - -// Kia is an api.Vehicle implementation with configurable getters and setters. +// Kia is an api.Vehicle implementation type Kia struct { *embed - *util.HTTPHelper - user string - password string - pin string - chargeG func() (float64, error) - auth kiaAuth -} - -type kiaAuth struct { - deviceID string - vehicleID string - controlToken string -} - -type response struct { - RetCode string `json:"retCode"` - ResMsg struct { - DeviceID string `json:"deviceId"` - EvStatus struct { - BatteryStatus float64 `json:"batteryStatus"` - } `json:"evStatus"` - Vehicles []struct { - VehicleID string `json:"vehicleId"` - } `json:"vehicles"` - } `json:"resMsg"` + *bluelink.API } func init() { @@ -69,326 +21,41 @@ func init() { // NewKiaFromConfig creates a new Vehicle func NewKiaFromConfig(other map[string]interface{}) (api.Vehicle, error) { cc := struct { - Title string - Capacity int64 - User, Password, VIN string - PIN string - Cache time.Duration + Title string + Capacity int64 + User, Password string + PIN string + Cache time.Duration }{} if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - v := &Kia{ - embed: &embed{cc.Title, cc.Capacity}, - HTTPHelper: util.NewHTTPHelper(util.NewLogger("kia")), - user: cc.User, - password: cc.Password, - pin: cc.PIN, + settings := bluelink.Config{ + URI: "https://prd.eu-ccapi.kia.com:8080", + TokenAuth: "ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==", + CCSPServiceID: "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a", + CCSPApplicationID: "693a33fa-c117-43f2-ae3b-61a02d24f417", + DeviceID: "/api/v1/spa/notifications/register", + Lang: "/api/v1/user/language", + Login: "/api/v1/user/signin", + AccessToken: "/api/v1/user/oauth2/token", + Vehicles: "/api/v1/spa/vehicles", + SendPIN: "/api/v1/user/pin", + GetStatus: "/api/v2/spa/vehicles/", } - // api is unbelievably slowwhen retrieving status - v.HTTPHelper.Client.Timeout = 60 * time.Second - - v.chargeG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - - return v, nil -} - -// request builds an HTTP request with headers and body -func (v *Kia) request(method, uri string, headers map[string]string, body io.Reader) (*http.Request, error) { - req, err := http.NewRequest(method, uri, body) - if err != nil { - return req, err - } - - for k, v := range headers { - req.Header.Add(k, v) - } - - return req, nil -} - -// jsonRequest builds an HTTP json request with headers and body -func (v *Kia) jsonRequest(method, uri string, headers map[string]string, data interface{}) (*http.Request, error) { - body, err := json.Marshal(data) + log := util.NewLogger("kia") + api, err := bluelink.New(log, cc.User, cc.Password, cc.PIN, cc.Cache, settings) if err != nil { return nil, err } - return v.request(method, uri, headers, bytes.NewReader(body)) -} - -// Credits to https://openwb.de/forum/viewtopic.php?f=5&t=1215&start=10#p11877 - -func (v *Kia) getDeviceID() (string, error) { - uniID, _ := uuid.NewUUID() - data := map[string]interface{}{ - "pushRegId": "1", - "pushType": "GCM", - "uuid": uniID.String(), - } - - headers := map[string]string{ - "ccsp-service-id": "fdc85c00-0a2f-4c64-bcb4-2cfb1500730a", - "Content-type": "application/json;charset=UTF-8", - "User-Agent": "okhttp/3.10.0", - } - - var resp response - req, err := v.jsonRequest(http.MethodPost, kiaURLDeviceID, headers, data) - if err == nil { - _, err = v.RequestJSON(req, &resp) - - if err == nil && resp.RetCode != resOK { - err = errors.New("unexpected response") - } - } - - return resp.ResMsg.DeviceID, err -} - -func (v *Kia) getCookies() (cookieClient *util.HTTPHelper, err error) { - cookieClient = util.NewHTTPHelper(v.Log) - cookieClient.Client.Jar, err = cookiejar.New(&cookiejar.Options{ - PublicSuffixList: publicsuffix.List, - }) - - if err == nil { - _, err = cookieClient.Get(kiaURLCookies) - } - - return cookieClient, err -} - -func (v *Kia) setLanguage(cookieClient *util.HTTPHelper) error { - headers := map[string]string{ - "Content-type": "application/json", - } - - data := map[string]interface{}{ - "lang": "en", - } - - req, err := v.jsonRequest(http.MethodPost, kiaURLLang, headers, data) - if err == nil { - _, err = cookieClient.Request(req) - } - - return err -} - -func (v *Kia) login(cookieClient *util.HTTPHelper) (string, error) { - headers := map[string]string{ - "Content-type": "application/json", - } - - data := map[string]interface{}{ - "email": v.user, - "password": v.password, - } - - req, err := v.jsonRequest(http.MethodPost, kiaURLLogin, headers, data) - if err != nil { - return "", err - } - - var redirect struct { - RedirectURL string `json:"redirectUrl"` - } - - var accCode string - if _, err = cookieClient.RequestJSON(req, &redirect); err == nil { - if parsed, err := url.Parse(redirect.RedirectURL); err == nil { - accCode = parsed.Query().Get("code") - } - } - - return accCode, err -} - -func (v *Kia) getToken(accCode string) (string, error) { - headers := map[string]string{ - "Authorization": "Basic ZmRjODVjMDAtMGEyZi00YzY0LWJjYjQtMmNmYjE1MDA3MzBhOnNlY3JldA==", - "Content-type": "application/x-www-form-urlencoded", - "User-Agent": "okhttp/3.10.0", - } - - data := "grant_type=authorization_code" + - "&redirect_uri=https%3A%2F%2Fprd.eu-ccapi.kia.com%3A8080%2Fapi%2Fv1%2Fuser%2Foauth2%2Fredirect" + - "&code=" + accCode - - req, err := v.request(http.MethodPost, kiaURLAccessToken, headers, strings.NewReader(data)) - if err != nil { - return "", err - } - - var tokens struct { - TokenType string `json:"token_type"` - AccessToken string `json:"access_token"` - } - - var accToken string - if _, err = v.RequestJSON(req, &tokens); err == nil { - accToken = fmt.Sprintf("%s %s", tokens.TokenType, tokens.AccessToken) - } - - return accToken, err -} - -func (v *Kia) getVehicles(accToken, did string) (string, error) { - headers := map[string]string{ - "Authorization": accToken, - "ccsp-device-id": did, - "ccsp-application-id": "693a33fa-c117-43f2-ae3b-61a02d24f417", - "offset": "1", - "User-Agent": "okhttp/3.10.0", - } - - req, err := v.request(http.MethodGet, kiaURLVehicles, headers, nil) - if err == nil { - var resp response - if _, err = v.RequestJSON(req, &resp); err == nil { - if resp.RetCode != resOK { - return "", errors.New("unexpected response") - } - - if len(resp.ResMsg.Vehicles) == 1 { - return resp.ResMsg.Vehicles[0].VehicleID, nil - } - - err = errors.New("couldn't find vehicle") - } - } - - return "", err -} - -func (v *Kia) preWakeup(accToken, did, vid string) error { - data := map[string]interface{}{ - "action": "prewakeup", - "deviceId": did, - } - - headers := map[string]string{ - "Authorization": accToken, - "ccsp-device-id": did, - "ccsp-application-id": "693a33fa-c117-43f2-ae3b-61a02d24f417", - "offset": "1", - "Content-Type": "application/json;charset=UTF-8", - "User-Agent": "okhttp/3.10.0", - } - - req, err := v.jsonRequest(http.MethodPost, kiaURLPreWakeup+vid+"/control/engine", headers, data) - if err == nil { - _, err = v.Request(req) - } - - return err -} - -func (v *Kia) sendPIN(deviceID, accToken string) (string, error) { - data := map[string]interface{}{ - "deviceId": deviceID, - "pin": string(v.pin), - } - - headers := map[string]string{ - "Authorization": accToken, - "Content-type": "application/json;charset=UTF-8", - "User-Agent": "okhttp/3.10.0", - } - - var token struct { - ControlToken string `json:"controlToken"` - } - - req, err := v.jsonRequest(http.MethodPut, kiaURLSendPIN, headers, data) - if err == nil { - _, err = v.RequestJSON(req, &token) - } - - controlToken := "" - if err == nil { - controlToken = "Bearer " + token.ControlToken - - } - - return controlToken, err -} - -func (v *Kia) authFlow() (err error) { - v.auth.deviceID, err = v.getDeviceID() - - var cookieClient *util.HTTPHelper - if err == nil { - cookieClient, err = v.getCookies() - } - - if err == nil { - err = v.setLanguage(cookieClient) - } - - var kiaAccCode string - if err == nil { - kiaAccCode, err = v.login(cookieClient) - } - - var kiaAccToken string - if err == nil { - kiaAccToken, err = v.getToken(kiaAccCode) - } - - if err == nil { - v.auth.vehicleID, err = v.getVehicles(kiaAccToken, v.auth.deviceID) - } - - if err == nil { - err = v.preWakeup(kiaAccToken, v.auth.deviceID, v.auth.vehicleID) - } - - if err == nil { - v.auth.controlToken, err = v.sendPIN(v.auth.deviceID, kiaAccToken) - } - - return err -} - -func (v *Kia) getStatus() (float64, error) { - headers := map[string]string{ - "Authorization": v.auth.controlToken, - "ccsp-device-id": v.auth.deviceID, - "Content-Type": "application/json", - } - - var resp response - req, err := v.request(http.MethodGet, kiaURLGetStatus+v.auth.vehicleID+"/status", headers, nil) - if err == nil { - _, err = v.RequestJSON(req, &resp) - - if err == nil && resp.RetCode != resOK { - err = errors.New("unexpected response") - } - } - - return resp.ResMsg.EvStatus.BatteryStatus, err -} - -// chargeState implements the Vehicle.ChargeState interface -func (v *Kia) chargeState() (float64, error) { - soc, err := v.getStatus() - - if err != nil && v.HTTPHelper.LastResponse().StatusCode == http.StatusUnauthorized { - if err = v.authFlow(); err == nil { - soc, err = v.getStatus() - } - } - - return soc, err -} - -// ChargeState implements the Vehicle.ChargeState interface -func (v *Kia) ChargeState() (float64, error) { - return v.chargeG() + v := &Kia{ + embed: &embed{cc.Title, cc.Capacity}, + API: api, + } + + return v, nil }