diff --git a/core/loadpoint.go b/core/loadpoint.go index 13f2e6e0e..ee0884870 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -1062,20 +1062,15 @@ func (lp *Loadpoint) charging() bool { func (lp *Loadpoint) PvChargeStarting() bool { lp.RLock() enabled := lp.enabled - pvTimer := lp.pvTimer + pvTimerRunning := !lp.pvTimer.IsZero() lp.RUnlock() if lp.GetMode() != api.ModePV || !lp.connected() || lp.chargeGoalReached(enabled) { return false } - if enabled || pvTimer.IsZero() { - return false - } - - // a timer restarting on every surplus dip never starts the loadpoint, hence - // only claim surplus once it has survived half of the enable delay (#32778) - return lp.clock.Since(pvTimer) >= lp.GetEnableDelay()/2 + // enable timer running (not yet enabled) + return !enabled && pvTimerRunning } // chargeGoalReached reports whether the loadpoint will not draw more: enabled diff --git a/core/site.go b/core/site.go index e558110a6..f1c47d0a0 100644 --- a/core/site.go +++ b/core/site.go @@ -1140,9 +1140,7 @@ func (site *Site) reservedPVPower(lp updater) float64 { continue } if other.EffectivePriority() > prio && other.PvChargeStarting() { - // min power is what the loadpoint needs to start, reserving its max - // power would starve this loadpoint beyond that (#32778) - reserved += other.EffectiveMinPower() + reserved += other.EffectiveMaxPower() } } diff --git a/core/site_prioritize_test.go b/core/site_prioritize_test.go index b6d4bcb20..aeaae88b8 100644 --- a/core/site_prioritize_test.go +++ b/core/site_prioritize_test.go @@ -10,10 +10,8 @@ import ( "github.com/evcc-io/evcc/util" ) -const testEnableDelay = time.Minute - func newPVLoadpoint(prio int, mode api.ChargeMode, status api.ChargeStatus, enabled bool, timer time.Time) *Loadpoint { - lp := &Loadpoint{ + return &Loadpoint{ log: util.NewLogger("lp"), clock: clock.NewMock(), minCurrent: minA, @@ -25,21 +23,13 @@ func newPVLoadpoint(prio int, mode api.ChargeMode, status api.ChargeStatus, enab pvTimer: timer, priority: prio, } - lp.Enable.Delay = testEnableDelay - return lp -} - -// pvTimerStarted returns a timer start that has been running for the given duration -func pvTimerStarted(running time.Duration) time.Time { - return clock.NewMock().Now().Add(-running) } func TestPvChargeStarting(t *testing.T) { now := clock.NewMock().Now() - settled := pvTimerStarted(testEnableDelay / 2) // enable timer running but car already full (soc at default 100% limit): not starting up - enablePendingFull := newPVLoadpoint(0, api.ModePV, api.StatusB, false, settled) + enablePendingFull := newPVLoadpoint(0, api.ModePV, api.StatusB, false, now) enablePendingFull.vehicleSoc = 100 tc := []struct { @@ -47,13 +37,12 @@ func TestPvChargeStarting(t *testing.T) { lp *Loadpoint starting bool }{ - {"enable timer running", newPVLoadpoint(0, api.ModePV, api.StatusB, false, settled), true}, - {"enable timer just restarted", newPVLoadpoint(0, api.ModePV, api.StatusB, false, now), false}, + {"enable timer running", newPVLoadpoint(0, api.ModePV, api.StatusB, false, now), true}, {"enabled not charging", newPVLoadpoint(0, api.ModePV, api.StatusB, true, time.Time{}), false}, {"enabled and charging", newPVLoadpoint(0, api.ModePV, api.StatusC, true, time.Time{}), false}, {"disabled idle", newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{}), false}, - {"disconnected", newPVLoadpoint(0, api.ModePV, api.StatusA, false, settled), false}, - {"not pv mode", newPVLoadpoint(0, api.ModeNow, api.StatusB, false, settled), false}, + {"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}, } @@ -68,7 +57,7 @@ func TestReservedPVPower(t *testing.T) { Voltage = 230 // higher-priority loadpoint (prio 1) starting up - high := newPVLoadpoint(1, api.ModePV, api.StatusB, false, pvTimerStarted(testEnableDelay/2)) + high := newPVLoadpoint(1, api.ModePV, api.StatusB, false, clock.NewMock().Now()) // lower-priority loadpoint (prio 0) in PV mode low := newPVLoadpoint(0, api.ModePV, api.StatusB, false, time.Time{}) @@ -77,20 +66,10 @@ func TestReservedPVPower(t *testing.T) { loadpoints: []*Loadpoint{high, low}, } - // low reserves the power high needs to start, not its max power (#32778) - if got, want := site.reservedPVPower(low), high.EffectiveMinPower(); got != want { + // low reserves high's anticipated max power while high is starting up + if got, want := site.reservedPVPower(low), high.EffectiveMaxPower(); got != want { t.Errorf("low: want %.0f, got %.0f", want, got) } - if high.EffectiveMinPower() == high.EffectiveMaxPower() { - t.Fatal("test requires min and max power to differ") - } - - // a timer restarting on every surplus dip must not reserve at all (#32778) - high.pvTimer = clock.NewMock().Now() - if got := site.reservedPVPower(low); got != 0 { - t.Errorf("low while high timer restarts: want 0, got %.0f", got) - } - high.pvTimer = pvTimerStarted(testEnableDelay / 2) // high (top priority) reserves nothing if got := site.reservedPVPower(high); got != 0 {