Hyundai/Kia: async soc refresh (#1015)
This commit is contained in:
parent
169d2ca11d
commit
0ec61aa14a
3 changed files with 108 additions and 31 deletions
|
|
@ -6,15 +6,23 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/andig/evcc/api"
|
||||
"github.com/andig/evcc/util"
|
||||
"github.com/andig/evcc/util/request"
|
||||
)
|
||||
|
||||
const resOK = "S" // auth fail: F
|
||||
const (
|
||||
VehiclesURL = "/api/v1/spa/vehicles"
|
||||
StatusURL = "/api/v1/spa/vehicles/%s/status"
|
||||
StatusLatestURL = "/api/v1/spa/vehicles/%s/status/latest"
|
||||
)
|
||||
|
||||
const (
|
||||
VehiclesURL = "/api/v1/spa/vehicles"
|
||||
StatusURL = "/api/v1/spa/vehicles/%s/status"
|
||||
resOK = "S" // auth fail: F
|
||||
timeFormat = "20060102150405 -0700" // Note: must add timeOffset
|
||||
timeOffset = " +0100"
|
||||
refreshTimeout = time.Minute
|
||||
statusExpiry = 5 * time.Minute
|
||||
)
|
||||
|
||||
// ErrAuthFail indicates authorization failure
|
||||
|
|
@ -24,8 +32,10 @@ var ErrAuthFail = errors.New("authorization failed")
|
|||
// Based on https://github.com/Hacksore/bluelinky.
|
||||
type API struct {
|
||||
*request.Helper
|
||||
log *util.Logger
|
||||
identity *Identity
|
||||
log *util.Logger
|
||||
identity *Identity
|
||||
refresh bool
|
||||
refreshTime time.Time
|
||||
}
|
||||
|
||||
// New creates a new BlueLink API
|
||||
|
|
@ -65,22 +75,39 @@ func (v *API) Vehicles() ([]Vehicle, error) {
|
|||
}
|
||||
|
||||
type StatusResponse struct {
|
||||
timestamp time.Time // add missing timestamp
|
||||
RetCode string
|
||||
ResMsg struct {
|
||||
EvStatus struct {
|
||||
BatteryStatus float64
|
||||
RemainTime2 struct {
|
||||
Atc struct {
|
||||
Value, Unit int
|
||||
}
|
||||
}
|
||||
DrvDistance []DrivingDistance
|
||||
RetCode string
|
||||
ResCode string
|
||||
ResMsg StatusData
|
||||
}
|
||||
|
||||
type StatusLatestResponse struct {
|
||||
RetCode string
|
||||
ResCode string
|
||||
ResMsg struct {
|
||||
VehicleStatusInfo struct {
|
||||
VehicleStatus StatusData
|
||||
}
|
||||
Vehicles []Vehicle
|
||||
}
|
||||
}
|
||||
|
||||
type StatusData struct {
|
||||
Time string
|
||||
EvStatus struct {
|
||||
BatteryStatus float64
|
||||
RemainTime2 struct {
|
||||
Atc struct {
|
||||
Value, Unit int
|
||||
}
|
||||
}
|
||||
DrvDistance []DrivingDistance
|
||||
}
|
||||
Vehicles []Vehicle
|
||||
}
|
||||
|
||||
func (d *StatusData) Updated() (time.Time, error) {
|
||||
return time.Parse(timeFormat, d.Time+timeOffset)
|
||||
}
|
||||
|
||||
type DrivingDistance struct {
|
||||
RangeByFuel struct {
|
||||
EvModeRange struct {
|
||||
|
|
@ -89,15 +116,64 @@ type DrivingDistance struct {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *API) Status(vid string) (StatusResponse, error) {
|
||||
var resp StatusResponse
|
||||
func (v *API) Status(vid string) (StatusData, error) {
|
||||
var resp StatusLatestResponse
|
||||
|
||||
req, err := v.identity.Request(http.MethodGet, fmt.Sprintf(StatusURL, vid))
|
||||
req, err := v.identity.Request(http.MethodGet, fmt.Sprintf(StatusLatestURL, vid))
|
||||
if err == nil {
|
||||
if err = v.DoJSON(req, &resp); err == nil && resp.RetCode != resOK {
|
||||
err = fmt.Errorf("unexpected response: %s", resp.RetCode)
|
||||
}
|
||||
|
||||
var ts time.Time
|
||||
if err == nil {
|
||||
ts, err = resp.ResMsg.VehicleStatusInfo.VehicleStatus.Updated()
|
||||
|
||||
// return the current value
|
||||
if time.Since(ts) <= statusExpiry {
|
||||
v.refresh = false
|
||||
return resp.ResMsg.VehicleStatusInfo.VehicleStatus, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
// request a refresh, irrespective of a previous error
|
||||
if !v.refresh {
|
||||
if err = v.refreshRequest(vid); err == nil {
|
||||
err = api.ErrMustRetry
|
||||
}
|
||||
|
||||
return StatusData{}, err
|
||||
}
|
||||
|
||||
// refresh finally expired
|
||||
if time.Since(v.refreshTime) > refreshTimeout {
|
||||
v.refresh = false
|
||||
if err == nil {
|
||||
err = api.ErrTimeout
|
||||
}
|
||||
} else {
|
||||
// wait for refresh, irrespective of a previous error
|
||||
err = api.ErrMustRetry
|
||||
}
|
||||
|
||||
return resp.ResMsg.VehicleStatusInfo.VehicleStatus, err
|
||||
}
|
||||
|
||||
func (v *API) refreshRequest(vid string) error {
|
||||
req, err := v.identity.Request(http.MethodGet, fmt.Sprintf(StatusURL, vid))
|
||||
if err == nil {
|
||||
v.refresh = true
|
||||
v.refreshTime = time.Now()
|
||||
|
||||
// run the actual update asynchronously
|
||||
go func() {
|
||||
var resp StatusResponse
|
||||
if err := v.DoJSON(req, &resp); err == nil && resp.RetCode != resOK {
|
||||
v.log.ERROR.Printf("unexpected response: %s", resp.RetCode)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ var _ api.Battery = (*Provider)(nil)
|
|||
func (v *Provider) SoC() (float64, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(StatusResponse); err == nil && ok {
|
||||
return float64(res.ResMsg.EvStatus.BatteryStatus), nil
|
||||
if res, ok := res.(StatusData); err == nil && ok {
|
||||
return float64(res.EvStatus.BatteryStatus), nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
|
|
@ -43,14 +43,15 @@ var _ api.VehicleFinishTimer = (*Provider)(nil)
|
|||
func (v *Provider) FinishTime() (time.Time, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(StatusResponse); err == nil && ok {
|
||||
remaining := res.ResMsg.EvStatus.RemainTime2.Atc.Value
|
||||
if res, ok := res.(StatusData); err == nil && ok {
|
||||
remaining := res.EvStatus.RemainTime2.Atc.Value
|
||||
|
||||
if remaining == 0 {
|
||||
return time.Time{}, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return res.timestamp.Add(time.Duration(remaining) * time.Minute), nil
|
||||
ts, err := res.Updated()
|
||||
return ts.Add(time.Duration(remaining) * time.Minute), err
|
||||
}
|
||||
|
||||
return time.Time{}, err
|
||||
|
|
@ -62,8 +63,8 @@ var _ api.VehicleRange = (*Provider)(nil)
|
|||
func (v *Provider) Range() (int64, error) {
|
||||
res, err := v.apiG()
|
||||
|
||||
if res, ok := res.(StatusResponse); err == nil && ok {
|
||||
if dist := res.ResMsg.EvStatus.DrvDistance; len(dist) == 1 {
|
||||
if res, ok := res.(StatusData); err == nil && ok {
|
||||
if dist := res.EvStatus.DrvDistance; len(dist) == 1 {
|
||||
return int64(dist[0].RangeByFuel.EvModeRange.Value), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const (
|
|||
fordAuth = "https://fcis.ice.ibmcloud.com"
|
||||
fordAPI = "https://usapi.cv.ford.com"
|
||||
fordVehicleList = "https://api.mps.ford.com/api/users/vehicles"
|
||||
fordOutdatedAfter = 5 * time.Minute // if returned status value is older, evcc will init refresh
|
||||
fordStatusExpiry = 5 * time.Minute // if returned status value is older, evcc will init refresh
|
||||
fordRefreshTimeout = time.Minute // timeout to get status after refresh
|
||||
fordTimeFormat = "01-02-2006 15:04:05" // time format used by Ford API, time is in UTC
|
||||
)
|
||||
|
|
@ -217,8 +217,8 @@ func (v *Ford) status() (res fordVehicleStatus, err error) {
|
|||
var lastUpdate time.Time
|
||||
lastUpdate, err = time.Parse(fordTimeFormat, res.VehicleStatus.LastRefresh)
|
||||
|
||||
if elapsed := time.Since(lastUpdate); err == nil && elapsed > fordOutdatedAfter {
|
||||
v.log.DEBUG.Printf("vehicle status is outdated (age %v > %v), requesting refresh", elapsed, fordOutdatedAfter)
|
||||
if elapsed := time.Since(lastUpdate); err == nil && elapsed > fordStatusExpiry {
|
||||
v.log.DEBUG.Printf("vehicle status is outdated (age %v > %v), requesting refresh", elapsed, fordStatusExpiry)
|
||||
|
||||
if err = v.refreshRequest(); err == nil {
|
||||
err = api.ErrMustRetry
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue