diff --git a/api/api.go b/api/api.go index 6c0098240..12f074841 100644 --- a/api/api.go +++ b/api/api.go @@ -82,7 +82,7 @@ type Vehicle interface { ChargeState() (float64, error) } -// ChargeRemainder provides current charge cycle duration -type ChargeRemainder interface { - RemainingTime() (time.Duration, error) +// ChargeFinishTimer provides estimated charge cycle finish time +type ChargeFinishTimer interface { + FinishTime() (time.Time, error) } diff --git a/cmd/dump.go b/cmd/dump.go index 5dce277e5..bc14e342f 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -49,11 +49,11 @@ func dumpAPIs(v interface{}) { } } - if v, ok := v.(api.ChargeRemainder); ok { - if duration, err := v.RemainingTime(); err != nil { - fmt.Printf("Remaining: %v\n", err) + if v, ok := v.(api.ChargeFinishTimer); ok { + if time, err := v.FinishTime(); err != nil { + fmt.Printf("Finish time: %v\n", err) } else { - fmt.Printf("Remaining: %v\n", duration) + fmt.Printf("Finish time: %v\n", time) } } diff --git a/core/wrapper/socestimator.go b/core/wrapper/socestimator.go index 1261211b3..d8e50ba78 100644 --- a/core/wrapper/socestimator.go +++ b/core/wrapper/socestimator.go @@ -54,9 +54,10 @@ func (s *SocEstimator) RemainingChargeDuration(chargePower float64, targetSoC in } // use vehicle api if available - if vr, ok := s.vehicle.(api.ChargeRemainder); ok { - timeRemaining, err := vr.RemainingTime() + if vr, ok := s.vehicle.(api.ChargeFinishTimer); ok { + finishTime, err := vr.FinishTime() if err == nil { + timeRemaining := time.Until(finishTime) return time.Duration(float64(timeRemaining) * percentRemaining / (100 - s.socCharge)) } diff --git a/provider/cache.go b/provider/cache.go index 07978c114..17d5ac298 100644 --- a/provider/cache.go +++ b/provider/cache.go @@ -112,3 +112,20 @@ func (c *Cached) DurationGetter() func() (time.Duration, error) { return c.val.(time.Duration), c.err } } + +// TimeGetter gets time.Time value +func (c *Cached) TimeGetter() func() (time.Time, error) { + g, ok := c.getter.(func() (time.Time, error)) + if !ok { + c.log.FATAL.Fatalf("invalid type: %T", c.getter) + } + + return func() (time.Time, error) { + if c.clock.Since(c.updated) > c.cache { + c.val, c.err = g() + c.updated = c.clock.Now() + } + + return c.val.(time.Time), c.err + } +} diff --git a/vehicle/audi.go b/vehicle/audi.go index a3ce8fb74..4fdd498d9 100644 --- a/vehicle/audi.go +++ b/vehicle/audi.go @@ -73,7 +73,7 @@ type Audi struct { brand, country string tokens audiTokenResponse chargeStateG func() (float64, error) - remainingTimeG func() (time.Duration, error) + finishTimeG func() (time.Time, error) } func init() { @@ -105,7 +105,7 @@ func NewAudiFromConfig(other map[string]interface{}) (api.Vehicle, error) { } v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - v.remainingTimeG = provider.NewCached(v.remainingTime, cc.Cache).DurationGetter() + v.finishTimeG = provider.NewCached(v.finishTime, cc.Cache).TimeGetter() var err error jar, err := cookiejar.New(&cookiejar.Options{ @@ -397,8 +397,8 @@ func (v *Audi) ChargeState() (float64, error) { return v.chargeStateG() } -// remainingTime implements the Vehicle.ChargeRemainder interface -func (v *Audi) remainingTime() (time.Duration, error) { +// finishTime implements the Vehicle.ChargeFinishTimer interface +func (v *Audi) finishTime() (time.Time, error) { var res audiChargerResponse uri := fmt.Sprintf("%s/bs/batterycharge/v1/%s/%s/vehicles/%s/charger", vwAPI, v.brand, v.country, v.vin) err := v.getJSON(uri, &res) @@ -408,10 +408,10 @@ func (v *Audi) remainingTime() (time.Duration, error) { timestamp, err = time.Parse(time.RFC3339, res.Charger.Status.BatteryStatusData.RemainingChargingTime.Timestamp) } - return time.Duration(res.Charger.Status.BatteryStatusData.RemainingChargingTime.Content)*time.Minute - time.Since(timestamp), err + return timestamp.Add(time.Duration(res.Charger.Status.BatteryStatusData.RemainingChargingTime.Content) * time.Minute), err } -// RemainingTime implements the Vehicle.ChargeRemainder interface -func (v *Audi) RemainingTime() (time.Duration, error) { - return v.remainingTimeG() +// FinishTime implements the Vehicle.ChargeFinishTimer interface +func (v *Audi) FinishTime() (time.Time, error) { + return v.finishTimeG() } diff --git a/vehicle/renault.go b/vehicle/renault.go index 08753a776..bbaf56358 100644 --- a/vehicle/renault.go +++ b/vehicle/renault.go @@ -81,7 +81,7 @@ type batteryAttributes struct { PlugStatus int `json:"plugStatus"` LastUpdateTime string `json:"lastUpdateTime"` ChargePower int `json:"chargePower"` - RemainingTime *int `json:"chargingRemainingTime"` + FinishTime *int `json:"chargingFinishTime"` } // Renault is an api.Vehicle implementation for Renault cars @@ -93,7 +93,7 @@ type Renault struct { gigyaJwtToken string accountID string chargeStateG func() (float64, error) - remainingTimeG func() (time.Duration, error) + finishTimeG func() (time.Time, error) } func init() { @@ -138,7 +138,7 @@ func NewRenaultFromConfig(other map[string]interface{}) (api.Vehicle, error) { } v.chargeStateG = provider.NewCached(v.chargeState, cc.Cache).FloatGetter() - v.remainingTimeG = provider.NewCached(v.remainingTime, cc.Cache).DurationGetter() + v.finishTimeG = provider.NewCached(v.finishTime, cc.Cache).TimeGetter() return v, nil } @@ -321,8 +321,8 @@ func (v *Renault) ChargeState() (float64, error) { return v.chargeStateG() } -// remainingTime implements the Vehicle.ChargeRemainder interface -func (v *Renault) remainingTime() (time.Duration, error) { +// finishTime implements the Vehicle.ChargeFinishTimer interface +func (v *Renault) finishTime() (time.Time, error) { uri := fmt.Sprintf("%s/commerce/v1/accounts/%s/kamereon/kca/car-adapter/v2/cars/%s/battery-status", v.kamereon.Target, v.accountID, v.vin) kr, err := v.kamereonRequest(uri) @@ -338,14 +338,14 @@ func (v *Renault) remainingTime() (time.Duration, error) { timestamp, err = time.Parse(time.RFC3339, kr.Data.Attributes.Timestamp) } - if kr.Data.Attributes.RemainingTime == nil { - return 0, errors.New("estimate not available") + if kr.Data.Attributes.FinishTime == nil { + return time.Time{}, errors.New("estimate not available") } - return time.Duration(*kr.Data.Attributes.RemainingTime)*time.Minute - time.Since(timestamp), err + return timestamp.Add(time.Duration(*kr.Data.Attributes.FinishTime) * time.Minute), err } -// RemainingTime implements the Vehicle.ChargeRemainder interface -func (v *Renault) RemainingTime() (time.Duration, error) { - return v.remainingTimeG() +// FinishTime implements the Vehicle.ChargeFinishTimer interface +func (v *Renault) FinishTime() (time.Time, error) { + return v.finishTimeG() }