Skip to content

Commit

Permalink
Planner: always create simple plan even if tariff errors (#19004)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Feb 21, 2025
1 parent 21cb9bb commit acbcf54
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 46 deletions.
2 changes: 1 addition & 1 deletion core/loadpoint/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions core/loadpoint/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 4 additions & 5 deletions core/loadpoint_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
14 changes: 7 additions & 7 deletions core/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}
58 changes: 34 additions & 24 deletions core/planner/planner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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")
}

Expand All @@ -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))
}

Expand All @@ -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 ")
}

Expand All @@ -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")
}

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions server/http_loadpoint_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit acbcf54

Please sign in to comment.