Loadpoint: don't reserve surplus when paused by feed-in priority (#33072)
This commit is contained in:
parent
cd08f3d36c
commit
c1067111e4
2 changed files with 84 additions and 1 deletions
|
|
@ -1047,7 +1047,9 @@ func (lp *Loadpoint) charging() bool {
|
|||
func (lp *Loadpoint) PvChargeStarting() bool {
|
||||
lp.RLock()
|
||||
enabled := lp.enabled
|
||||
pvTimerRunning := !lp.pvTimer.IsZero()
|
||||
// an elapsed timer means a delay was skipped, e.g. by a feed-in pause, not an
|
||||
// enable pending, hence such a loadpoint claims no surplus
|
||||
pvTimerRunning := !lp.pvTimer.IsZero() && !lp.pvTimer.Equal(elapsed)
|
||||
lp.RUnlock()
|
||||
|
||||
if lp.GetMode() != api.ModePV || !lp.connected() || lp.chargeGoalReached(enabled) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
evbus "github.com/asaskevich/EventBus"
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/core/loadpoint"
|
||||
|
|
@ -44,6 +45,8 @@ func TestPvChargeStarting(t *testing.T) {
|
|||
{"disconnected", newPVLoadpoint(0, api.ModePV, api.StatusA, false, now), false},
|
||||
{"not pv mode", newPVLoadpoint(0, api.ModeNow, api.StatusB, false, now), false},
|
||||
{"enable pending but car full", enablePendingFull, false},
|
||||
// elapsed means a delay was skipped, e.g. by a feed-in pause, not an enable pending
|
||||
{"timer elapsed", newPVLoadpoint(0, api.ModePV, api.StatusB, false, elapsed), false},
|
||||
}
|
||||
|
||||
for _, tc := range tc {
|
||||
|
|
@ -91,4 +94,82 @@ func TestReservedPVPower(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// feedInCharger is a stateful charger, unlike the mocks used elsewhere, since the
|
||||
// feed-in pause is asserted across multiple update cycles
|
||||
type feedInCharger struct {
|
||||
status api.ChargeStatus
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func (c *feedInCharger) Status() (api.ChargeStatus, error) { return c.status, nil }
|
||||
func (c *feedInCharger) Enabled() (bool, error) { return c.enabled, nil }
|
||||
func (c *feedInCharger) MaxCurrent(int64) error { return nil }
|
||||
|
||||
func (c *feedInCharger) Enable(v bool) error {
|
||||
c.enabled = v
|
||||
// a disabled charger stops drawing, i.e. reports connected instead of charging
|
||||
if !v && c.status == api.StatusC {
|
||||
c.status = api.StatusB
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestReservedPVPowerSmartFeedInPause asserts that a loadpoint paused by its smart feed-in
|
||||
// priority limit does not reserve surplus: it is meant to export instead of charge
|
||||
func TestReservedPVPowerSmartFeedInPause(t *testing.T) {
|
||||
// the feed-in rate rises above the limit while the loadpoint is idle or charging
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
status api.ChargeStatus
|
||||
enabled bool
|
||||
}{
|
||||
{"idle", api.StatusB, false},
|
||||
{"charging", api.StatusC, true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
clck := clock.NewMock()
|
||||
limit := 0.05
|
||||
|
||||
car := newPVLoadpoint(1, api.ModePV, tc.status, tc.enabled, time.Time{})
|
||||
car.bus = evbus.New()
|
||||
car.clock = clck
|
||||
car.charger = &feedInCharger{status: tc.status, enabled: tc.enabled}
|
||||
car.chargeMeter = &Null{} // silence nil panics
|
||||
car.chargeRater = &Null{} // silence nil panics
|
||||
car.chargeTimer = &Null{} // silence nil panics
|
||||
car.wakeUpTimer = NewTimer()
|
||||
car.smartFeedInPriorityLimit = &limit
|
||||
car.vehicleSoc = 20
|
||||
car.limitSoc = 80
|
||||
attachListeners(t, car)
|
||||
|
||||
low := newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{})
|
||||
|
||||
site := &Site{
|
||||
log: util.NewLogger("site"),
|
||||
loadpoints: []*Loadpoint{car, low},
|
||||
}
|
||||
|
||||
// checkSmartLimit looks up rates by wall clock time, not lp.clock
|
||||
now := time.Now()
|
||||
feedin := api.Rates{{Start: now.Add(-time.Hour), End: now.Add(time.Hour), Value: limit + 0.01}}
|
||||
|
||||
// the pause holds across cycles, so must the absence of a reservation
|
||||
for i := range 2 {
|
||||
car.Update(-3500, 0, nil, feedin, false, false, 0, nil, nil, nil)
|
||||
|
||||
if car.enabled {
|
||||
t.Fatalf("cycle %d: car must be paused by the feed-in limit", i)
|
||||
}
|
||||
|
||||
if got := site.reservedPVPower(low); got != 0 {
|
||||
t.Errorf("cycle %d: paused car reserves %.0fW, want 0W (pvTimer %v)", i, got, car.pvTimer)
|
||||
}
|
||||
|
||||
clck.Add(time.Minute)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var _ loadpoint.API = (*Loadpoint)(nil)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue