Battery Boost: allow scaling to 3p while active (#32776)
Co-authored-by: andig <cpuidle@gmail.com>
This commit is contained in:
parent
f47dd16e61
commit
f7e7ab887e
2 changed files with 130 additions and 0 deletions
|
|
@ -1590,6 +1590,16 @@ func (lp *Loadpoint) boostPower(batteryPower float64) float64 {
|
|||
delta += lp.EffectiveStepPower()
|
||||
}
|
||||
|
||||
// bridge the power gap between 1p max and 3p min so pvScalePhases can trigger a scale-up
|
||||
if lp.hasPhaseSwitching() && lp.phaseSwitchCompleted() && lp.site.GetBatteryMaxDischargePower() != nil {
|
||||
if activePhases, maxPhases := lp.ActivePhases(), lp.MaxActivePhases(); activePhases < maxPhases &&
|
||||
lp.circuitAllowsPhases(maxPhases, lp.effectiveMinCurrent()) {
|
||||
// max power actually achievable on the active phases
|
||||
activeMaxPower := min(lp.EffectiveMaxPower(), Voltage*lp.effectiveMaxCurrent()*float64(activePhases))
|
||||
delta += max(0, lp.EffectiveMinPower()*float64(maxPhases)-activeMaxPower)
|
||||
}
|
||||
}
|
||||
|
||||
// start boosting by setting maximum power
|
||||
if boost == boostStart {
|
||||
delta = lp.EffectiveMaxPower()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package core
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/core/site"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
type mockSite struct {
|
||||
|
|
@ -104,3 +106,121 @@ func TestBoostPower(t *testing.T) {
|
|||
// res = max(0, -2000) + 790 + 0 = 790W
|
||||
assert.Equal(t, 790.0, res)
|
||||
}
|
||||
|
||||
type plainCharger struct {
|
||||
api.Charger
|
||||
}
|
||||
|
||||
type phaseSwitchCharger struct {
|
||||
api.Charger
|
||||
}
|
||||
|
||||
func (phaseSwitchCharger) Phases1p3p(int) error { return nil }
|
||||
|
||||
func TestBoostPowerPhaseSwitchGapBridging(t *testing.T) {
|
||||
Voltage = 230
|
||||
lp := &Loadpoint{
|
||||
log: util.NewLogger("lp"),
|
||||
status: api.StatusC,
|
||||
charger: phaseSwitchCharger{},
|
||||
batteryBoost: boostContinue,
|
||||
minCurrent: 6,
|
||||
maxCurrent: 16,
|
||||
phases: 1,
|
||||
phasesConfigured: 3,
|
||||
}
|
||||
s := &mockSite{}
|
||||
lp.site = s
|
||||
|
||||
// boostContinue on 1p with phase switching: delta must cover the gap
|
||||
// between 1p@16A (3680W) and 3p@6A (4140W) = 460W, plus the
|
||||
// base delta (100W) and coarse step power (1p: 230W)
|
||||
limit := 10000.0
|
||||
s.maxDischargePower = &limit
|
||||
s.residualPower = 0
|
||||
res := lp.boostPower(0)
|
||||
// delta = 100 (base) + 230 (step@1p) + 460 (gap) = 790
|
||||
// res = 0 + 790 + 0 = 790
|
||||
assert.Equal(t, 790.0, res)
|
||||
// verify gap alone exceeds the 3p minimum threshold
|
||||
// available_power ≈ chargePower(3680) + boostReturn(790) = 4470 > 4140
|
||||
assert.Greater(t, Voltage*16+res, Voltage*6*3, "boost must bridge 1p-3p gap")
|
||||
|
||||
// already on 3p: no phase gap added, only base + step
|
||||
lp.phases = 3
|
||||
res = lp.boostPower(0)
|
||||
// delta = 100 + 690 (step@3p) = 790
|
||||
assert.Equal(t, 790.0, res)
|
||||
}
|
||||
|
||||
func TestBoostPowerPhaseSwitchGapBridgingExclusions(t *testing.T) {
|
||||
Voltage = 230
|
||||
limit10k := 10000.0
|
||||
now := time.Now()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
charger api.Charger
|
||||
phasesSwitched time.Time
|
||||
maxDischargePower *float64
|
||||
circuitPower float64
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
name: "no phase switching",
|
||||
charger: plainCharger{},
|
||||
maxDischargePower: &limit10k,
|
||||
circuitPower: 10000,
|
||||
expected: 330,
|
||||
},
|
||||
{
|
||||
name: "phase switch not completed",
|
||||
charger: phaseSwitchCharger{},
|
||||
phasesSwitched: now,
|
||||
maxDischargePower: &limit10k,
|
||||
circuitPower: 10000,
|
||||
expected: 330,
|
||||
},
|
||||
{
|
||||
name: "no max discharge power limit",
|
||||
charger: phaseSwitchCharger{},
|
||||
maxDischargePower: nil,
|
||||
circuitPower: 10000,
|
||||
expected: 330,
|
||||
},
|
||||
{
|
||||
name: "circuit not allowing 3p",
|
||||
charger: phaseSwitchCharger{},
|
||||
maxDischargePower: &limit10k,
|
||||
circuitPower: 0,
|
||||
expected: 330,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
circuit := api.NewMockCircuit(ctrl)
|
||||
circuit.EXPECT().ValidatePower(gomock.Any(), gomock.Any()).Return(tc.circuitPower).AnyTimes()
|
||||
|
||||
lp := &Loadpoint{
|
||||
log: util.NewLogger("lp"),
|
||||
status: api.StatusC,
|
||||
charger: tc.charger,
|
||||
batteryBoost: boostContinue,
|
||||
minCurrent: 6,
|
||||
maxCurrent: 16,
|
||||
phases: 1,
|
||||
phasesConfigured: 3,
|
||||
phasesSwitched: tc.phasesSwitched,
|
||||
circuit: circuit,
|
||||
}
|
||||
s := &mockSite{
|
||||
maxDischargePower: tc.maxDischargePower,
|
||||
}
|
||||
lp.site = s
|
||||
|
||||
res := lp.boostPower(0)
|
||||
assert.Equal(t, tc.expected, res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue