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())
|
return lp.setLimit(lp.effectiveMinCurrent())
|
||||||
}
|
}
|
||||||
|
|
||||||
// pvScalePhases switches phases if necessary and returns number of phases switched to
|
// pvScalePhases switches phases if necessary and returns number of phases switched to.
|
||||||
func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) int {
|
// 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()
|
phases := lp.GetPhases()
|
||||||
|
|
||||||
// observed phase state inconsistency
|
// 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
|
// while charging, scaling down only helps if 1p is sustainable, otherwise it
|
||||||
// merely delays the pv disable timer by the phase timer duration
|
// merely delays the pv disable timer by the phase timer duration. Without a
|
||||||
useful := !lp.enabled || !lp.charging() || powerToCurrent(availablePower, 1) >= minCurrent
|
// 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 {
|
if insufficient && !useful {
|
||||||
lp.log.DEBUG.Printf("available power %.0fW < %.0fW min 1p threshold, disabling instead of scaling down", availablePower, Voltage*minCurrent)
|
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
|
// push demand to drain battery
|
||||||
sitePower -= lp.boostPower(batteryPower)
|
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
|
// switch phases up/down
|
||||||
var scaledTo int
|
var scaledTo int
|
||||||
if lp.hasPhaseSwitching() && lp.phaseSwitchCompleted() {
|
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
|
// 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)
|
targetCurrent := max(effectiveCurrent+deltaCurrent, 0)
|
||||||
|
|
||||||
// in MinPV mode or under special conditions return at least minCurrent
|
// 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)
|
lp.log.DEBUG.Printf("pv charge current: min %.3gA > %.3gA (%.0fW @ %dp, battery: %t)", minCurrent, targetCurrent, sitePower, activePhases, battery)
|
||||||
return minCurrent
|
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
|
// 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
|
// 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 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)
|
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 strings.Contains(testExpectation, testDirection) {
|
||||||
if scaled == 0 {
|
if scaled == 0 {
|
||||||
|
|
@ -384,6 +384,13 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
||||||
lp.chargePower = 3 * Voltage * minA
|
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
|
// 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) {
|
{"3/0->1, not enough power, not charging", 3, 0, 0, 1, 1, func(lp *Loadpoint) {
|
||||||
lp.status = api.StatusB
|
lp.status = api.StatusB
|
||||||
|
|
@ -437,7 +444,7 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
||||||
charger.MockPhaseSwitcher.EXPECT().Phases1p3p(tc.toPhases).Return(nil)
|
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.res, res, tc.desc)
|
||||||
require.Equal(t, tc.toPhases, lp.phases, tc.desc)
|
require.Equal(t, tc.toPhases, lp.phases, tc.desc)
|
||||||
|
|
@ -785,7 +792,7 @@ func TestPvScalePhasesCircuitLimits(t *testing.T) {
|
||||||
}{plainCharger, phaseCharger},
|
}{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()
|
ctrl.Finish()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue