Battery boost: hold at soc limit instead of disabling (#31922)

This commit is contained in:
Alexandre JARDON 2026-07-19 11:06:18 +02:00 • committed by GitHub
parent a5956a6804
commit 8fcd443171
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 8 deletions

View file

@ -63,6 +63,7 @@ const (
boostDisabled = 0
boostStart = 1
boostContinue = 2
boostHold = 3 // soc limit reached: stop draining but keep vehicle priority over recharging
)
// elapsed is the time an expired timer will be set to
@ -1523,7 +1524,7 @@ func (lp *Loadpoint) publishTimer(name string, delay time.Duration, action strin
// boostPower returns the additional power that the loadpoint should draw from the battery
func (lp *Loadpoint) boostPower(batteryBoostPower float64) float64 {
boost := lp.GetBatteryBoost()
if boost == boostDisabled {
if boost == boostDisabled || boost == boostHold {
return 0
}
@ -2042,15 +2043,15 @@ func (lp *Loadpoint) phaseSwitchCompleted() bool {
// Update is the main control function. It reevaluates meters and charger state
func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, consumption, feedin api.Rates, batteryBuffered, batteryStart bool, greenShare float64, effPrice, effCo2 *float64, dim *bool) {
// auto-disable battery boost when SOC drops below limit
if lp.GetBatteryBoost() != boostDisabled {
// hold battery boost when SOC drops below the limit: stop draining the battery, but
// keep the vehicle prioritised over recharging it (via sitePower priorityAdjustment)
// until the vehicle disconnects. This holds the battery at the configured level
// instead of the naive on/off which lets the battery recharge and oscillate (#30558).
if boost := lp.GetBatteryBoost(); boost != boostDisabled && boost != boostHold {
if limit := lp.GetBatteryBoostLimit(); limit < 100 {
if batterySoc := lp.site.GetBatterySoc(); batterySoc < float64(limit) {
lp.log.DEBUG.Printf("battery boost disabled: soc below limit (%.0f%% < %d%%)", batterySoc, limit)
if err := lp.SetBatteryBoost(false); err != nil {
lp.log.ERROR.Printf("set battery boost: %v", err)
}
lp.log.DEBUG.Printf("battery boost hold: soc below limit (%.0f%% < %d%%)", batterySoc, limit)
lp.setBatteryBoost(boostHold)
}
}
}

View file

@ -863,3 +863,22 @@ func TestWelcomeChargeAppliedOnlyOnce(t *testing.T) {
welcomeCharge, _ = lp.updateChargerStatus()
assert.False(t, welcomeCharge)
}
// TestBatteryBoostHold verifies that in the hold state (soc limit reached) battery
// boost no longer draws power from the battery, while still counting as active so
// the site keeps prioritising the vehicle over recharging the battery (#30558).
func TestBatteryBoostHold(t *testing.T) {
lp := &Loadpoint{log: util.NewLogger("foo")}
// disabled draws nothing
lp.batteryBoost = boostDisabled
assert.Equal(t, 0.0, lp.boostPower(2000), "disabled")
// hold draws nothing (stops draining the battery)...
lp.batteryBoost = boostHold
assert.Equal(t, 0.0, lp.boostPower(2000), "hold")
// ...but is still an active boost state (GetBatteryBoost != boostDisabled),
// which is what keeps the sitePower priority adjustment applied to the loadpoint
assert.NotEqual(t, boostDisabled, lp.GetBatteryBoost(), "hold is active")
}