Planner: always create simple plan even if tariff errors (#19004)

This commit is contained in:
andig 2025-02-21 08:09:48 +01:00 • committed by GitHub
parent 21cb9bb110
commit acbcf5461c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 54 additions and 46 deletions

View file

@ -116,7 +116,7 @@ type API interface {
// SocBasedPlanning determines if the planner is soc based
SocBasedPlanning() bool
// GetPlan creates a charging plan
GetPlan(targetTime time.Time, requiredDuration time.Duration) (api.Rates, error)
GetPlan(targetTime time.Time, requiredDuration time.Duration) api.Rates
// GetSocConfig returns the soc poll settings
GetSocConfig() SocConfig

View file

@ -420,12 +420,11 @@ func (mr *MockAPIMockRecorder) GetPhasesConfigured() *gomock.Call {
}
// GetPlan mocks base method.
func (m *MockAPI) GetPlan(targetTime time.Time, requiredDuration time.Duration) (api.Rates, error) {
func (m *MockAPI) GetPlan(targetTime time.Time, requiredDuration time.Duration) api.Rates {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPlan", targetTime, requiredDuration)
ret0, _ := ret[0].(api.Rates)
ret1, _ := ret[1].(error)
return ret0, ret1
return ret0
}
// GetPlan indicates an expected call of GetPlan.

View file

@ -79,9 +79,9 @@ func (lp *Loadpoint) GetPlanGoal() (float64, bool) {
}
// GetPlan creates a charging plan for given time and duration
func (lp *Loadpoint) GetPlan(targetTime time.Time, requiredDuration time.Duration) (api.Rates, error) {
func (lp *Loadpoint) GetPlan(targetTime time.Time, requiredDuration time.Duration) api.Rates {
if lp.planner == nil || targetTime.IsZero() {
return nil, nil
return nil
}
return lp.planner.Plan(requiredDuration, targetTime)
@ -132,9 +132,8 @@ func (lp *Loadpoint) plannerActive() (active bool) {
return false
}
plan, err := lp.GetPlan(planTime, requiredDuration)
if err != nil {
lp.log.ERROR.Println("planner:", err)
plan := lp.GetPlan(planTime, requiredDuration)
if plan == nil {
return false
}

View file

@ -136,9 +136,9 @@ func (t *Planner) continuousPlan(rates api.Rates, start, end time.Time) api.Rate
return res
}
func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) (api.Rates, error) {
func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) api.Rates {
if t == nil || requiredDuration <= 0 {
return nil, nil
return nil
}
latestStart := targetTime.Add(-requiredDuration)
@ -157,19 +157,19 @@ func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) (ap
// target charging without tariff or late start
if t.tariff == nil {
return simplePlan, nil
return simplePlan
}
rates, err := t.tariff.Rates()
// treat like normal target charging if we don't have rates
if len(rates) == 0 || err != nil {
return simplePlan, err
return simplePlan
}
// consume remaining time
if t.clock.Until(targetTime) <= requiredDuration {
return t.continuousPlan(rates, latestStart, targetTime), nil
return t.continuousPlan(rates, latestStart, targetTime)
}
// rates are by default sorted by date, oldest to newest
@ -183,7 +183,7 @@ func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) (ap
// there is enough time for charging after end of current rates
durationAfterRates := targetTime.Sub(last)
if durationAfterRates >= requiredDuration {
return nil, nil
return nil
}
// need to use some of the available slots
@ -199,5 +199,5 @@ func (t *Planner) Plan(requiredDuration time.Duration, targetTime time.Time) (ap
// sort plan by time
plan.Sort()
return plan, nil
return plan
}

View file

@ -133,8 +133,29 @@ func TestNilTariff(t *testing.T) {
clock: clock,
}
plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
require.NoError(t, err)
plan := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
assert.Equal(t, api.Rates{
{
Start: clock.Now(),
End: clock.Now().Add(60 * time.Minute),
},
}, plan, "expected simple plan")
}
func TestRatesError(t *testing.T) {
clock := clock.NewMock()
ctrl := gomock.NewController(t)
trf := api.NewMockTariff(ctrl)
trf.EXPECT().Rates().AnyTimes().Return(nil, api.ErrOutdated)
p := &Planner{
log: util.NewLogger("foo"),
clock: clock,
tariff: trf,
}
plan := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
assert.Equal(t, api.Rates{
{
Start: clock.Now(),
@ -163,12 +184,10 @@ func TestFlatTariffTargetInThePast(t *testing.T) {
},
}
plan, err := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
require.NoError(t, err)
plan := p.Plan(time.Hour, clock.Now().Add(30*time.Minute))
assert.Equal(t, simplePlan, plan, "expected simple plan")
plan, err = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute))
require.NoError(t, err)
plan = p.Plan(time.Hour, clock.Now().Add(-30*time.Minute))
assert.Equal(t, simplePlan, plan, "expected simple plan")
}
@ -189,14 +208,12 @@ func TestFlatTariffLongSlots(t *testing.T) {
// that slots are not longer than 1 hour and with that context this is not a problem
// expect 00:00-01:00 UTC
plan, err := p.Plan(time.Hour, clock.Now().Add(2*time.Hour))
require.NoError(t, err)
plan := p.Plan(time.Hour, clock.Now().Add(2*time.Hour))
assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan))
assert.Equal(t, api.Rate{}, SlotAt(clock.Now().Add(time.Hour), plan))
// expect 00:00-01:00 UTC
plan, err = p.Plan(time.Hour, clock.Now().Add(time.Hour))
require.NoError(t, err)
plan = p.Plan(time.Hour, clock.Now().Add(time.Hour))
assert.Equal(t, api.Rate{Start: clock.Now(), End: clock.Now().Add(time.Hour)}, SlotAt(clock.Now(), plan))
}
@ -213,12 +230,10 @@ func TestTargetAfterKnownPrices(t *testing.T) {
tariff: trf,
}
plan, err := p.Plan(40*time.Minute, clock.Now().Add(2*time.Hour)) // charge efficiency does not allow to test with 1h
require.NoError(t, err)
plan := p.Plan(40*time.Minute, clock.Now().Add(2*time.Hour)) // charge efficiency does not allow to test with 1h
assert.False(t, !SlotAt(clock.Now(), plan).IsZero(), "should not start if car can be charged completely after known prices ")
plan, err = p.Plan(2*time.Hour, clock.Now().Add(2*time.Hour))
require.NoError(t, err)
plan = p.Plan(2*time.Hour, clock.Now().Add(2*time.Hour))
assert.True(t, !SlotAt(clock.Now(), plan).IsZero(), "should start if car can not be charged completely after known prices ")
}
@ -242,12 +257,10 @@ func TestChargeAfterTargetTime(t *testing.T) {
},
}
plan, err := p.Plan(time.Hour, clock.Now())
require.NoError(t, err)
plan := p.Plan(time.Hour, clock.Now())
assert.Equal(t, simplePlan, plan, "expected simple plan")
plan, err = p.Plan(time.Hour, clock.Now().Add(-time.Hour))
require.NoError(t, err)
plan = p.Plan(time.Hour, clock.Now().Add(-time.Hour))
assert.Equal(t, simplePlan, plan, "expected simple plan")
}
@ -259,8 +272,7 @@ func TestContinuousPlanNoTariff(t *testing.T) {
clock: clock,
}
plan, err := p.Plan(time.Hour, clock.Now())
require.NoError(t, err)
plan := p.Plan(time.Hour, clock.Now())
// single-slot plan
assert.Len(t, plan, 1)
@ -281,8 +293,7 @@ func TestContinuousPlan(t *testing.T) {
tariff: trf,
}
plan, err := p.Plan(150*time.Minute, clock.Now())
require.NoError(t, err)
plan := p.Plan(150*time.Minute, clock.Now())
// 3-slot plan
assert.Len(t, plan, 3)
@ -301,8 +312,7 @@ func TestContinuousPlanOutsideRates(t *testing.T) {
tariff: trf,
}
plan, err := p.Plan(30*time.Minute, clock.Now())
require.NoError(t, err)
plan := p.Plan(30*time.Minute, clock.Now())
// 3-slot plan
assert.Len(t, plan, 1)

View file

@ -50,8 +50,8 @@ func planHandler(lp loadpoint.API) http.HandlerFunc {
goal, _ := lp.GetPlanGoal()
requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower)
plan, err := lp.GetPlan(planTime, requiredDuration)
if err != nil {
plan := lp.GetPlan(planTime, requiredDuration)
if plan == nil {
w.WriteHeader(http.StatusBadRequest)
return
}
@ -109,8 +109,8 @@ func staticPlanPreviewHandler(lp loadpoint.API) http.HandlerFunc {
maxPower := lp.EffectiveMaxPower()
requiredDuration := lp.GetPlanRequiredDuration(goal, maxPower)
plan, err := lp.GetPlan(planTime, requiredDuration)
if err != nil {
plan := lp.GetPlan(planTime, requiredDuration)
if plan == nil {
w.WriteHeader(http.StatusBadRequest)
return
}
@ -162,8 +162,8 @@ func repeatingPlanPreviewHandler(lp loadpoint.API) http.HandlerFunc {
maxPower := lp.EffectiveMaxPower()
requiredDuration := lp.GetPlanRequiredDuration(soc, maxPower)
plan, err := lp.GetPlan(planTime, requiredDuration)
if err != nil {
plan := lp.GetPlan(planTime, requiredDuration)
if plan == nil {
w.WriteHeader(http.StatusBadRequest)
return
}