diff --git a/api/error.go b/api/error.go index 817ab0a08..ce4d78acc 100644 --- a/api/error.go +++ b/api/error.go @@ -26,3 +26,11 @@ type errTimeoutError struct{} func (errTimeoutError) Error() string { return "timeout" } func (errTimeoutError) Timeout() bool { return true } func (errTimeoutError) Temporary() bool { return true } + +// ErrAsleep indicates that vehicle is asleep. Caller may chose to wake up the vehicle and retry. +var ErrAsleep error = errAsleep{} + +type errAsleep struct{} + +func (errAsleep) Error() string { return "asleep" } +func (errAsleep) Unwrap() error { return ErrTimeout } diff --git a/core/loadpoint.go b/core/loadpoint.go index 9b1789981..ca2232abe 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -665,6 +665,14 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64, force bool) error { } if err != nil { + v := lp.GetVehicle() + if vv, ok := v.(api.Resurrector); ok && errors.Is(err, api.ErrAsleep) { + // https://github.com/evcc-io/evcc/issues/8254 + // wakeup vehicle + lp.log.DEBUG.Printf("max charge current: waking up vehicle") + return vv.WakeUp() + } + return fmt.Errorf("max charge current %.3gA: %w", chargeCurrent, err) } diff --git a/vehicle/tesla.go b/vehicle/tesla.go index 2e2adff41..acec79910 100644 --- a/vehicle/tesla.go +++ b/vehicle/tesla.go @@ -2,6 +2,7 @@ package vehicle import ( "context" + "errors" "time" "github.com/bogosj/tesla" @@ -76,20 +77,29 @@ func NewTeslaFromConfig(other map[string]interface{}) (api.Vehicle, error) { v.Title_ = v.vehicle.DisplayName } - v.dataG = provider.Cached(v.vehicle.Data, cc.Cache) + v.dataG = provider.Cached(func() (*tesla.VehicleData, error) { + res, err := v.vehicle.Data() + return res, v.apiError(err) + }, cc.Cache) return v, nil } +// apiError converts HTTP 408 error to ErrTimeout +func (v *Tesla) apiError(err error) error { + if err != nil && err.Error() == "408 Request Timeout" { + err = api.ErrAsleep + } + return err +} + // Soc implements the api.Vehicle interface func (v *Tesla) Soc() (float64, error) { res, err := v.dataG() - - if err == nil { - return float64(res.Response.ChargeState.UsableBatteryLevel), nil + if err != nil { + return 0, err } - - return 0, err + return float64(res.Response.ChargeState.UsableBatteryLevel), nil } var _ api.ChargeState = (*Tesla)(nil) @@ -188,51 +198,29 @@ var _ api.CurrentLimiter = (*Tesla)(nil) // StartCharge implements the api.VehicleChargeController interface func (v *Tesla) MaxCurrent(current int64) error { - return v.vehicle.SetChargingAmps(int(current)) + return v.apiError(v.vehicle.SetChargingAmps(int(current))) } var _ api.Resurrector = (*Tesla)(nil) func (v *Tesla) WakeUp() error { _, err := v.vehicle.Wakeup() - return err + return v.apiError(err) } var _ api.VehicleChargeController = (*Tesla)(nil) // StartCharge implements the api.VehicleChargeController interface func (v *Tesla) StartCharge() error { - err := v.vehicle.StartCharging() - - if err != nil && err.Error() == "408 Request Timeout" { - if _, err := v.vehicle.Wakeup(); err != nil { - return err - } - - timer := time.NewTimer(90 * time.Second) - - for { - select { - case <-timer.C: - return api.ErrTimeout - default: - time.Sleep(2 * time.Second) - if err := v.vehicle.StartCharging(); err == nil || err.Error() != "408 Request Timeout" { - return err - } - } - } - } - - return err + return v.apiError(v.vehicle.StartCharging()) } // StopCharge implements the api.VehicleChargeController interface func (v *Tesla) StopCharge() error { - err := v.vehicle.StopCharging() + err := v.apiError(v.vehicle.StopCharging()) // ignore sleeping vehicle - if err != nil && err.Error() == "408 Request Timeout" { + if errors.Is(err, api.ErrAsleep) { err = nil }