Phases: allow 1p scale-down when the loadpoint never disables (#33214)
This commit is contained in:
parent
10b0c99f17
commit
8d425d3920
2 changed files with 23 additions and 10 deletions
|
|
@ -1438,8 +1438,9 @@ func (lp *Loadpoint) minCharging() error {
|
|||
return lp.setLimit(lp.effectiveMinCurrent())
|
||||
}
|
||||
|
||||
// pvScalePhases switches phases if necessary and returns number of phases switched to
|
||||
func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) int {
|
||||
// pvScalePhases switches phases if necessary and returns number of phases switched to.
|
||||
// mayDisable indicates that insufficient surplus can stop charging via the pv disable timer.
|
||||
func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64, mayDisable bool) int {
|
||||
phases := lp.GetPhases()
|
||||
|
||||
// observed phase state inconsistency
|
||||
|
|
@ -1466,8 +1467,9 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) in
|
|||
}
|
||||
|
||||
// while charging, scaling down only helps if 1p is sustainable, otherwise it
|
||||
// merely delays the pv disable timer by the phase timer duration
|
||||
useful := !lp.enabled || !lp.charging() || powerToCurrent(availablePower, 1) >= minCurrent
|
||||
// merely delays the pv disable timer by the phase timer duration. Without a
|
||||
// disable to wait for, scaling down is the only way to reduce power (#33208).
|
||||
useful := !lp.enabled || !lp.charging() || !mayDisable || powerToCurrent(availablePower, 1) >= minCurrent
|
||||
if insufficient && !useful {
|
||||
lp.log.DEBUG.Printf("available power %.0fW < %.0fW min 1p threshold, disabling instead of scaling down", availablePower, Voltage*minCurrent)
|
||||
}
|
||||
|
|
@ -1638,10 +1640,14 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower, batteryPower f
|
|||
// push demand to drain battery
|
||||
sitePower -= lp.boostPower(batteryPower)
|
||||
|
||||
// minpv and the battery conditions hold charging at min current, no disable can follow
|
||||
battery := batteryStart || batteryBuffered && lp.charging() || lp.GetBatteryBoost() == boostContinue
|
||||
mayDisable := mode == api.ModePV && !battery
|
||||
|
||||
// switch phases up/down
|
||||
var scaledTo int
|
||||
if lp.hasPhaseSwitching() && lp.phaseSwitchCompleted() {
|
||||
scaledTo = lp.pvScalePhases(sitePower, minCurrent, maxCurrent)
|
||||
scaledTo = lp.pvScalePhases(sitePower, minCurrent, maxCurrent, mayDisable)
|
||||
}
|
||||
|
||||
// calculate target charge current from delta power and actual current
|
||||
|
|
@ -1659,7 +1665,7 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower, batteryPower f
|
|||
targetCurrent := max(effectiveCurrent+deltaCurrent, 0)
|
||||
|
||||
// in MinPV mode or under special conditions return at least minCurrent
|
||||
if battery := batteryStart || batteryBuffered && lp.charging() || lp.GetBatteryBoost() == boostContinue; (mode == api.ModeMinPV || battery) && targetCurrent < minCurrent {
|
||||
if (mode == api.ModeMinPV || battery) && targetCurrent < minCurrent {
|
||||
lp.log.DEBUG.Printf("pv charge current: min %.3gA > %.3gA (%.0fW @ %dp, battery: %t)", minCurrent, targetCurrent, sitePower, activePhases, battery)
|
||||
return minCurrent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string,
|
|||
// scale-up should only execute when the 1p max current is exceeded
|
||||
// we're testing this here and remove the upscale expectation for the following test below 1p max current
|
||||
if maxAmp := -sitePower / Voltage; maxAmp < maxA {
|
||||
if scaled := lp.pvScalePhases(sitePower, minA, maxAmp-0.0001); scaled != 3 {
|
||||
if scaled := lp.pvScalePhases(sitePower, minA, maxAmp-0.0001, true); scaled != 3 {
|
||||
t.Errorf("%v act=%d max=%d missing scale %s at reduced max current %.1fA", tc, act, max, direction, maxAmp)
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string,
|
|||
}
|
||||
}
|
||||
|
||||
scaled := lp.pvScalePhases(sitePower, minA, maxA)
|
||||
scaled := lp.pvScalePhases(sitePower, minA, maxA, true)
|
||||
|
||||
if strings.Contains(testExpectation, testDirection) {
|
||||
if scaled == 0 {
|
||||
|
|
@ -384,6 +384,13 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
|||
lp.chargePower = 3 * Voltage * minA
|
||||
}},
|
||||
|
||||
// minpv never disables, so scale down even if 1p is not sustainable (#33208)
|
||||
{"3/3->1, insufficient for 1p, charging, minpv", 3, 3, 0.1, 1, 1, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
lp.enabled = true
|
||||
lp.mode = api.ModeMinPV
|
||||
}},
|
||||
|
||||
// switch down from 3p/0p while not yet charging
|
||||
{"3/0->1, not enough power, not charging", 3, 0, 0, 1, 1, func(lp *Loadpoint) {
|
||||
lp.status = api.StatusB
|
||||
|
|
@ -437,7 +444,7 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
|||
charger.MockPhaseSwitcher.EXPECT().Phases1p3p(tc.toPhases).Return(nil)
|
||||
}
|
||||
|
||||
res := lp.pvScalePhases(tc.sitePower, minA, maxA)
|
||||
res := lp.pvScalePhases(tc.sitePower, minA, maxA, lp.mode != api.ModeMinPV)
|
||||
|
||||
require.Equal(t, tc.res, res, tc.desc)
|
||||
require.Equal(t, tc.toPhases, lp.phases, tc.desc)
|
||||
|
|
@ -785,7 +792,7 @@ func TestPvScalePhasesCircuitLimits(t *testing.T) {
|
|||
}{plainCharger, phaseCharger},
|
||||
}
|
||||
|
||||
require.Equal(t, tc.expectedPhases, lp.pvScalePhases(tc.sitePower, lp.minCurrent, lp.maxCurrent))
|
||||
require.Equal(t, tc.expectedPhases, lp.pvScalePhases(tc.sitePower, lp.minCurrent, lp.maxCurrent, true))
|
||||
|
||||
ctrl.Finish()
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue