Loadpoint: fix fast charging phase scaling (#27972)

This commit is contained in:
mfuchs1984 2026-03-07 09:46:46 +01:00 • committed by GitHub
parent b6fdd75231
commit 56c1d56626
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 4 deletions

View file

@ -1282,12 +1282,14 @@ func (lp *Loadpoint) scalePhases(phases int) error {
func (lp *Loadpoint) fastCharging() error {
if lp.hasPhaseSwitching() {
phases := 3
maxPower1p := Voltage * lp.effectiveMaxCurrent()
// load management limit active
if circuitMaxPower := circuitMaxPower(lp.circuit); circuitMaxPower > 0 && circuitMaxPower < 1.1*maxPower1p {
phases = 1
lp.log.DEBUG.Printf("fast charging: scaled to 1p to match %.0fW max circuit power", circuitMaxPower)
if lp.circuit != nil {
minPower3p := currentToPower(lp.effectiveMinCurrent(), 3)
if powerLimit := lp.circuit.ValidatePower(lp.chargePower, minPower3p); powerLimit < minPower3p {
phases = 1
lp.log.DEBUG.Printf("fast charging: scaled to 1p to match %.0fW available circuit power", powerLimit)
}
}
if err := lp.scalePhasesIfAvailable(phases); err != nil {

View file

@ -479,3 +479,74 @@ func TestScalePhasesIfAvailable(t *testing.T) {
ctrl.Finish()
}
}
func TestFastChargingCircuitBasedPhaseScaling(t *testing.T) {
Voltage = 230
tc := []struct {
desc string
phases int
chargePower float64
availableCircuitPower float64 // ValidatePower return for 3p request
expectedPhases int
noCircuit bool
}{
{desc: "no circuit", phases: 3, chargePower: 0, expectedPhases: 3, noCircuit: true},
{desc: "low limit, no surplus", phases: 3, chargePower: 0, availableCircuitPower: 3680, expectedPhases: 1},
{desc: "low limit, with surplus", phases: 1, chargePower: 0, availableCircuitPower: 11040, expectedPhases: 3},
{desc: "already charging, low limit", phases: 3, chargePower: 3680, availableCircuitPower: 3680, expectedPhases: 1},
{desc: "already charging, high limit", phases: 1, chargePower: 3680, availableCircuitPower: 11040, expectedPhases: 3},
{desc: "edge case: just below 3p minimum", phases: 3, chargePower: 0, availableCircuitPower: 4140 - 1, expectedPhases: 1},
{desc: "edge case: just at 3p minimum", phases: 1, chargePower: 0, availableCircuitPower: 4140, expectedPhases: 3},
}
for _, tc := range tc {
t.Run(tc.desc, func(t *testing.T) {
ctrl := gomock.NewController(t)
lp := NewLoadpoint(util.NewLogger("foo"), nil)
lp.minCurrent = 6
lp.maxCurrent = 16
lp.phases = tc.phases
lp.chargePower = tc.chargePower
lp.offeredCurrent = 0 // ensure MaxCurrent is called
lp.wakeUpTimer = NewTimer()
plainCharger := api.NewMockCharger(ctrl)
phaseCharger := api.NewMockPhaseSwitcher(ctrl)
lp.charger = struct {
*api.MockCharger
*api.MockPhaseSwitcher
}{plainCharger, phaseCharger}
if !tc.noCircuit {
circuit := api.NewMockCircuit(ctrl)
lp.circuit = circuit
minPower3p := Voltage * 6 * 3
// fastCharging call to ValidatePower
circuit.EXPECT().ValidatePower(tc.chargePower, minPower3p).Return(tc.availableCircuitPower)
// setLimit calls
circuit.EXPECT().ValidateCurrent(gomock.Any(), lp.maxCurrent).Return(lp.maxCurrent)
circuit.EXPECT().ValidatePower(tc.chargePower, float64(tc.expectedPhases)*Voltage*lp.maxCurrent).Return(float64(tc.expectedPhases) * Voltage * lp.maxCurrent)
}
plainCharger.EXPECT().Enabled().Return(true, nil).AnyTimes()
plainCharger.EXPECT().Enable(gomock.Any()).Return(nil).AnyTimes()
if tc.phases != tc.expectedPhases {
phaseCharger.EXPECT().Phases1p3p(tc.expectedPhases).Return(nil)
}
plainCharger.EXPECT().MaxCurrent(int64(lp.maxCurrent)).Return(nil)
err := lp.fastCharging()
require.NoError(t, err)
require.Equal(t, tc.expectedPhases, lp.phases, tc.desc)
ctrl.Finish()
})
}
}