Fix limiting PV current when scaling phases up (#13295)
This commit is contained in:
parent
cc24f44a61
commit
f78913eae5
2 changed files with 52 additions and 55 deletions
|
|
@ -1062,8 +1062,8 @@ func (lp *Loadpoint) fastCharging() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// pvScalePhases switches phases if necessary and returns if switch occurred
|
||||
func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bool {
|
||||
// pvScalePhases switches phases if necessary and returns number of phases switched to
|
||||
func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) int {
|
||||
phases := lp.GetPhases()
|
||||
|
||||
// observed phase state inconsistency
|
||||
|
|
@ -1102,7 +1102,7 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bo
|
|||
if err := lp.scalePhases(1); err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
return true
|
||||
return 1
|
||||
}
|
||||
|
||||
waiting = true
|
||||
|
|
@ -1131,7 +1131,10 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bo
|
|||
if err := lp.scalePhases(3); err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
return true
|
||||
if err := lp.scalePhases(3); err != nil {
|
||||
lp.log.ERROR.Println(err)
|
||||
}
|
||||
return 3
|
||||
}
|
||||
|
||||
waiting = true
|
||||
|
|
@ -1142,7 +1145,7 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bo
|
|||
lp.resetPhaseTimer()
|
||||
}
|
||||
|
||||
return false
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO move up to timer functions
|
||||
|
|
@ -1174,13 +1177,18 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter
|
|||
maxCurrent := lp.effectiveMaxCurrent()
|
||||
|
||||
// switch phases up/down
|
||||
var scaledTo int
|
||||
if lp.hasPhaseSwitching() && lp.phaseSwitchCompleted() {
|
||||
_ = lp.pvScalePhases(sitePower, minCurrent, maxCurrent)
|
||||
scaledTo = lp.pvScalePhases(sitePower, minCurrent, maxCurrent)
|
||||
}
|
||||
|
||||
// calculate target charge current from delta power and actual current
|
||||
effectiveCurrent := lp.effectiveCurrent()
|
||||
activePhases := lp.ActivePhases()
|
||||
effectiveCurrent := lp.effectiveCurrent()
|
||||
if scaledTo == 3 {
|
||||
// if we did scale, adjust the effective current to the new phase count
|
||||
effectiveCurrent /= 3.0
|
||||
}
|
||||
deltaCurrent := powerToCurrent(-sitePower, activePhases)
|
||||
targetCurrent := max(effectiveCurrent+deltaCurrent, 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/benbjohnson/clock"
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
|
|
@ -115,9 +116,7 @@ func TestMaxActivePhases(t *testing.T) {
|
|||
expectedPhases = dflt
|
||||
}
|
||||
|
||||
if phs := lp.maxActivePhases(); phs != expectedPhases {
|
||||
t.Errorf("expected max %d, got %d", expectedPhases, phs)
|
||||
}
|
||||
require.Equal(t, expectedPhases, lp.maxActivePhases(), "expected max active phases")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,7 +135,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 {
|
||||
if scaled := lp.pvScalePhases(sitePower, minA, maxAmp-0.0001); scaled != 3 {
|
||||
t.Errorf("%v act=%d max=%d missing scale %s at reduced max current %.1fA", tc, act, max, direction, maxAmp)
|
||||
}
|
||||
|
||||
|
|
@ -148,10 +147,10 @@ func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string,
|
|||
scaled := lp.pvScalePhases(sitePower, minA, maxA)
|
||||
|
||||
if strings.Contains(testExpectation, testDirection) {
|
||||
if !scaled {
|
||||
if scaled == 0 {
|
||||
t.Errorf("%v act=%d max=%d missing scale %s", tc, act, max, direction)
|
||||
}
|
||||
} else if scaled {
|
||||
} else if scaled != 0 {
|
||||
t.Errorf("%v act=%d max=%d unexpected scale %s", tc, act, max, direction)
|
||||
}
|
||||
}
|
||||
|
|
@ -217,16 +216,10 @@ func TestPvScalePhases(t *testing.T) {
|
|||
t.Fatalf("%v invalid test case", tc)
|
||||
}
|
||||
|
||||
if lp.phases != tc.physical {
|
||||
t.Error("wrong phases", lp.phases, tc.physical)
|
||||
}
|
||||
require.Equal(t, tc.physical, lp.phases, "wrong phases")
|
||||
require.Equal(t, tc.actExpected, lp.ActivePhases(), "expected active phases")
|
||||
require.Equal(t, tc.maxExpected, lp.maxActivePhases(), "expected max active phases")
|
||||
|
||||
if phs := lp.ActivePhases(); phs != tc.actExpected {
|
||||
t.Errorf("expected active %d, got %d", tc.actExpected, phs)
|
||||
}
|
||||
if phs := lp.maxActivePhases(); phs != tc.maxExpected {
|
||||
t.Errorf("expected max %d, got %d", tc.maxExpected, phs)
|
||||
}
|
||||
ctrl.Finish()
|
||||
|
||||
// scaling
|
||||
|
|
@ -276,83 +269,83 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
|||
phases, measuredPhases int
|
||||
sitePower float64
|
||||
toPhases int
|
||||
res bool
|
||||
res int
|
||||
prepare func(lp *Loadpoint)
|
||||
}{
|
||||
// switch up from 1p/1p configured/active
|
||||
{"1/1->3, not enough power", 1, 1, 0, 1, false, nil},
|
||||
{"1/1->3, kickoff", 1, 1, -3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
|
||||
{"1/1->3, not enough power", 1, 1, 0, 1, 0, nil},
|
||||
{"1/1->3, kickoff", 1, 1, -3 * Voltage * minA, 1, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = time.Time{}
|
||||
}},
|
||||
{"1/1->3, timer running", 1, 1, -3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
|
||||
{"1/1->3, timer running", 1, 1, -3 * Voltage * minA, 1, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = lp.clock.Now()
|
||||
}},
|
||||
{"1/1->3, timer elapsed", 1, 1, -3 * Voltage * minA, 3, true, func(lp *Loadpoint) {
|
||||
{"1/1->3, timer elapsed", 1, 1, -3 * Voltage * minA, 3, 3, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
}},
|
||||
|
||||
// omit to switch up (again) from 3p/1p configured/active
|
||||
{"3/1->3, not enough power", 3, 1, 0, 3, false, nil},
|
||||
{"3/1->3, kickoff", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
// omit to switch up (again) from 3p/1p configured/a0ctive
|
||||
{"3/1->3, not enough power", 3, 1, 0, 3, 0, nil},
|
||||
{"3/1->3, kickoff", 3, 1, -3 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = time.Time{}
|
||||
}},
|
||||
{"3/1->3, timer running", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
{"3/1->3, timer running", 3, 1, -3 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = lp.clock.Now()
|
||||
}},
|
||||
{"3/1->3, timer elapsed", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
{"3/1->3, timer elapsed", 3, 1, -3 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
}},
|
||||
|
||||
// omit to switch down from 3p/1p configured/active
|
||||
{"3/1->1, not enough power", 3, 1, 0, 3, false, nil},
|
||||
{"3/1->1, kickoff", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
{"3/1->1, not enough power", 3, 1, 0, 3, 0, nil},
|
||||
{"3/1->1, kickoff", 3, 1, -1 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = time.Time{}
|
||||
}},
|
||||
{"3/1->1, timer running", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
{"3/1->1, timer running", 3, 1, -1 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = lp.clock.Now()
|
||||
}},
|
||||
{"3/1->1, timer elapsed", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
|
||||
{"3/1->1, timer elapsed", 3, 1, -1 * Voltage * minA, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
}},
|
||||
|
||||
// switch down from 3p/3p configured/active
|
||||
{"3/3->1, enough power", 3, 3, 0, 3, false, nil},
|
||||
{"3/3->1, enough power, timer elapsed, load point enabled", 3, 3, 0, 3, false, func(lp *Loadpoint) {
|
||||
{"3/3->1, enough power", 3, 3, 0, 3, 0, nil},
|
||||
{"3/3->1, enough power, timer elapsed, load point enabled", 3, 3, 0, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
lp.enabled = true
|
||||
}},
|
||||
{"3/3->1, enough power, timer elapsed, load point disabled", 3, 3, 0, 1, true, func(lp *Loadpoint) {
|
||||
{"3/3->1, enough power, timer elapsed, load point disabled", 3, 3, 0, 1, 1, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
lp.enabled = false
|
||||
}},
|
||||
{"3/3->1, kickoff", 3, 3, 0.1, 3, false, func(lp *Loadpoint) {
|
||||
{"3/3->1, kickoff", 3, 3, 0.1, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = time.Time{}
|
||||
}},
|
||||
{"3/3->1, timer running", 3, 3, 0.1, 3, false, func(lp *Loadpoint) {
|
||||
{"3/3->1, timer running", 3, 3, 0.1, 3, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = lp.clock.Now()
|
||||
}},
|
||||
{"3/3->1, timer elapsed", 3, 3, 0.1, 1, true, func(lp *Loadpoint) {
|
||||
{"3/3->1, timer elapsed", 3, 3, 0.1, 1, 1, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
}},
|
||||
|
||||
// switch down from 3p/0p while not yet charging
|
||||
{"3/0->1, not enough power, not charging", 3, 0, 0, 1, true, func(lp *Loadpoint) {
|
||||
{"3/0->1, not enough power, not charging", 3, 0, 0, 1, 1, func(lp *Loadpoint) {
|
||||
lp.status = api.StatusB
|
||||
}},
|
||||
// switch up from 1p/0p while not yet charging
|
||||
{"1/0->3, enough power, not charging", 1, 0, -3 * Voltage * minA, 3, true, func(lp *Loadpoint) {
|
||||
{"1/0->3, enough power, not charging", 1, 0, -3 * Voltage * minA, 3, 3, func(lp *Loadpoint) {
|
||||
lp.status = api.StatusB
|
||||
}},
|
||||
|
||||
// error states from 1p/3p misconfiguration - no correction for time being (stay at 1p)
|
||||
{"1/3->1, enough power", 1, 3, -1 * Voltage * maxA, 1, false, nil},
|
||||
{"1/3->1, kickoff, correct phase setting", 1, 3, 0.1, 1, false, func(lp *Loadpoint) {
|
||||
{"1/3->1, enough power", 1, 3, -1 * Voltage * maxA, 1, 0, nil},
|
||||
{"1/3->1, kickoff, correct phase setting", 1, 3, 0.1, 1, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = time.Time{}
|
||||
}},
|
||||
{"1/3->1, timer running, correct phase setting", 1, 3, 0.1, 1, false, func(lp *Loadpoint) {
|
||||
{"1/3->1, timer running, correct phase setting", 1, 3, 0.1, 1, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = lp.clock.Now()
|
||||
}},
|
||||
{"1/3->1, switch not executed", 1, 3, 0.1, 1, false, func(lp *Loadpoint) {
|
||||
{"1/3->1, switch not executed", 1, 3, 0.1, 1, 0, func(lp *Loadpoint) {
|
||||
lp.phaseTimer = elapsed
|
||||
}},
|
||||
}
|
||||
|
|
@ -383,18 +376,14 @@ func TestPvScalePhasesTimer(t *testing.T) {
|
|||
tc.prepare(lp)
|
||||
}
|
||||
|
||||
if tc.res {
|
||||
if tc.res != 0 {
|
||||
charger.MockPhaseSwitcher.EXPECT().Phases1p3p(tc.toPhases).Return(nil)
|
||||
}
|
||||
|
||||
res := lp.pvScalePhases(tc.sitePower, minA, maxA)
|
||||
|
||||
switch {
|
||||
case tc.res != res:
|
||||
t.Errorf("expected %v, got %v", tc.res, res)
|
||||
case lp.phases != tc.toPhases:
|
||||
t.Errorf("expected %dp, got %dp", tc.toPhases, lp.phases)
|
||||
}
|
||||
require.Equal(t, tc.res, res, tc.desc)
|
||||
require.Equal(t, tc.toPhases, lp.phases, tc.desc)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue