Skip to content

Commit

Permalink
Bluelink: add charge status and odometer (#1923)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Nov 24, 2021
1 parent 4c5020a commit 3c0e429
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 14 deletions.
5 changes: 3 additions & 2 deletions vehicle/bluelink/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ func (v *API) Vehicles() ([]Vehicle, error) {
return res.ResMsg.Vehicles, err
}

func (v *API) Status(vid string) (StatusLatestResponse, error) {
// StatusLatest retrieves the latest server-side status
func (v *API) StatusLatest(vid string) (StatusLatestResponse, error) {
var res StatusLatestResponse

uri := fmt.Sprintf("%s/%s", v.baseURI, fmt.Sprintf(StatusLatestURL, vid))
Expand All @@ -79,7 +80,7 @@ func (v *API) Status(vid string) (StatusLatestResponse, error) {
return res, err
}

// StatusPartial refreshes the status from the bluelink api
// StatusPartial refreshes the status
func (v *API) StatusPartial(vid string) (StatusResponse, error) {
var res StatusResponse

Expand Down
52 changes: 45 additions & 7 deletions vehicle/bluelink/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const refreshTimeout = 2 * time.Minute
// Based on https://github.com/Hacksore/bluelinky.
type Provider struct {
statusG func() (interface{}, error)
statusLG func() (interface{}, error)
refreshG func() (StatusResponse, error)
expiry time.Duration
refreshTime time.Time
Expand All @@ -29,15 +30,19 @@ func NewProvider(api *API, vid string, expiry, cache time.Duration) *Provider {

v.statusG = provider.NewCached(func() (interface{}, error) {
return v.status(
func() (StatusLatestResponse, error) { return api.Status(vid) },
func() (StatusLatestResponse, error) { return api.StatusLatest(vid) },
)
}, cache).InterfaceGetter()

v.statusLG = provider.NewCached(func() (interface{}, error) {
return api.StatusLatest(vid)
}, cache).InterfaceGetter()

return v
}

// status wraps the api status call and adds status refresh
func (v *Provider) status(statusG func() (StatusLatestResponse, error)) (StatusData, error) {
func (v *Provider) status(statusG func() (StatusLatestResponse, error)) (VehicleStatus, error) {
res, err := statusG()

var ts time.Time
Expand Down Expand Up @@ -69,7 +74,7 @@ func (v *Provider) status(statusG func() (StatusLatestResponse, error)) (StatusD
err = api.ErrMustRetry
}

return StatusData{}, err
return VehicleStatus{}, err
}

// refresh finally expired
Expand All @@ -83,7 +88,7 @@ func (v *Provider) status(statusG func() (StatusLatestResponse, error)) (StatusD
err = api.ErrMustRetry
}

return StatusData{}, err
return VehicleStatus{}, err
}

var _ api.Battery = (*Provider)(nil)
Expand All @@ -92,20 +97,40 @@ var _ api.Battery = (*Provider)(nil)
func (v *Provider) SoC() (float64, error) {
res, err := v.statusG()

if res, ok := res.(StatusData); err == nil && ok {
if res, ok := res.(VehicleStatus); err == nil && ok {
return res.EvStatus.BatteryStatus, nil
}

return 0, err
}

var _ api.ChargeState = (*Provider)(nil)

// Status implements the api.Battery interface
func (v *Provider) Status() (api.ChargeStatus, error) {
res, err := v.statusG()

status := api.StatusNone
if res, ok := res.(VehicleStatus); err == nil && ok {
status = api.StatusA
if res.EvStatus.BatteryPlugin > 0 {
status = api.StatusB
}
if res.EvStatus.BatteryCharge {
status = api.StatusC
}
}

return status, err
}

var _ api.VehicleFinishTimer = (*Provider)(nil)

// FinishTime implements the api.VehicleFinishTimer interface
func (v *Provider) FinishTime() (time.Time, error) {
res, err := v.statusG()

if res, ok := res.(StatusData); err == nil && ok {
if res, ok := res.(VehicleStatus); err == nil && ok {
remaining := res.EvStatus.RemainTime2.Atc.Value

if remaining == 0 {
Expand All @@ -125,7 +150,7 @@ var _ api.VehicleRange = (*Provider)(nil)
func (v *Provider) Range() (int64, error) {
res, err := v.statusG()

if res, ok := res.(StatusData); err == nil && ok {
if res, ok := res.(VehicleStatus); err == nil && ok {
if dist := res.EvStatus.DrvDistance; len(dist) == 1 {
return int64(dist[0].RangeByFuel.EvModeRange.Value), nil
}
Expand All @@ -135,3 +160,16 @@ func (v *Provider) Range() (int64, error) {

return 0, err
}

var _ api.VehicleOdometer = (*Provider)(nil)

// Range implements the api.VehicleRange interface
func (v *Provider) Odometer() (float64, error) {
res, err := v.statusLG()

if res, ok := res.(StatusLatestResponse); err == nil && ok {
return res.ResMsg.VehicleStatusInfo.Odometer.Value, nil
}

return 0, err
}
24 changes: 20 additions & 4 deletions vehicle/bluelink/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,27 @@ type VehiclesResponse struct {
type StatusResponse struct {
RetCode string
ResCode string
ResMsg StatusData
ResMsg VehicleStatus
}

type StatusLatestResponse struct {
RetCode string
ResCode string
ResMsg struct {
VehicleStatusInfo struct {
VehicleStatus StatusData
VehicleStatus VehicleStatus
VehicleLocation VehicleLocation
Odometer Odometer
}
}
}

type StatusData struct {
type VehicleStatus struct {
Time string
EvStatus struct {
BatteryCharge bool
BatteryStatus float64
BatteryPlugin int
RemainTime2 struct {
Atc struct {
Value, Unit int
Expand All @@ -39,12 +43,24 @@ type StatusData struct {
Vehicles []Vehicle
}

type VehicleLocation struct {
Coord struct {
Lat, Lon, Alt float64
}
Time string // TODO convert to timestamp
}

type Odometer struct {
Value float64
Unit int
}

const (
timeFormat = "20060102150405 -0700" // Note: must add timeOffset
timeOffset = " +0100"
)

func (d *StatusData) Updated() (time.Time, error) {
func (d *VehicleStatus) Updated() (time.Time, error) {
return time.Parse(timeFormat, d.Time+timeOffset)
}

Expand Down
3 changes: 2 additions & 1 deletion vehicle/hyundai.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ func NewHyundaiFromConfig(other map[string]interface{}) (api.Vehicle, error) {
var vehicle bluelink.Vehicle
if cc.VIN == "" && len(vehicles) == 1 {
vehicle = vehicles[0]
log.DEBUG.Printf("found vehicle: %v", cc.VIN)
log.DEBUG.Printf("found vehicle: %v", vehicle.VIN)
} else {
for _, v := range vehicles {
if v.VIN == strings.ToUpper(cc.VIN) {
vehicle = v
log.DEBUG.Printf("found vehicle: %v", vehicle.VIN)
}
}
}
Expand Down

0 comments on commit 3c0e429

Please sign in to comment.