Revert "PV: reserve only min power for higher-priority loadpoints starting up" (#32947)

This commit is contained in:
andig 2026-08-18 12:38:26 +02:00 • committed by GitHub
parent 88b641301a
commit 6d276526a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 12 additions and 40 deletions

View file

@ -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

View file

@ -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()
}
}

View file

@ -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 {