Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
weblate committed Oct 13, 2024
2 parents b694a9f + d9a4152 commit 0a9d7d6
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 59 deletions.
2 changes: 1 addition & 1 deletion core/loadpoint/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type API interface {
EffectivePriority() int
// EffectivePlanTime returns the effective plan time
EffectivePlanTime() time.Time
// EffectiveMinPower returns the min charging power for a single phase
// EffectiveMinPower returns the min charging power for the minimum active phases
EffectiveMinPower() float64
// EffectiveMaxPower returns the max charging power taking active phases into account
EffectiveMaxPower() float64
Expand Down
5 changes: 2 additions & 3 deletions core/loadpoint_effective.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,9 @@ func (lp *Loadpoint) effectiveLimitSoc() int {
return 100
}

// EffectiveMinPower returns the effective min power for a single phase
// EffectiveMinPower returns the effective min power for the minimum active phases
func (lp *Loadpoint) EffectiveMinPower() float64 {
// TODO check if 1p available
return Voltage * lp.effectiveMinCurrent()
return Voltage * lp.effectiveMinCurrent() * float64(lp.minActivePhases())
}

// EffectiveMaxPower returns the effective max power taking vehicle capabilities and phase scaling into account
Expand Down
20 changes: 18 additions & 2 deletions core/loadpoint_phases.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,37 @@ func (lp *Loadpoint) ActivePhases() int {
return active
}

// maxActivePhases returns the maximum number of active phases for the meter.
// minActivePhases returns the minimum number of active phases for the loadpoint.
func (lp *Loadpoint) minActivePhases() int {
lp.Lock()
configuredPhases := lp.configuredPhases
lp.Unlock()

// 1p3p supported or limit 1p
if lp.hasPhaseSwitching() || configuredPhases == 1 {
return 1
}

return lp.maxActivePhases()
}

// maxActivePhases returns the maximum number of active phases for the loadpoint.
func (lp *Loadpoint) maxActivePhases() int {
physical := lp.GetPhases()
measured := lp.getMeasuredPhases()
vehicle := lp.getVehiclePhases()
charger := lp.getChargerPhysicalPhases()

// during 1p or unknown config, 1p measured is not a restriction
if physical <= 1 || vehicle == 1 {
if physical <= 1 || vehicle == 1 || charger == 1 {
measured = 0
}

// if 1p3p supported then assume configured limit or 3p
if lp.hasPhaseSwitching() {
lp.Lock()
physical = lp.configuredPhases
lp.Unlock()
}

return min(expect(vehicle), expect(physical), expect(measured), expect(charger))
Expand Down
147 changes: 94 additions & 53 deletions core/loadpoint_phases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,106 +17,147 @@ type testCase struct {
// capable=0 signals 1p3p as set during loadpoint init
// physical/vehicle=0 signals unknown
// measuredPhases<>0 signals previous measurement
capable, physical, vehicle, measuredPhases, actExpected, maxExpected int
capable, physical, vehicle, measuredPhases, actExpected, maxExpected, minExpected int
// scaling expectation: d=down, u=up, du=both
scale string
}

var phaseTests = []testCase{
// 1p
{1, 1, 0, 0, 1, 1, ""},
{1, 1, 0, 1, 1, 1, ""},
{1, 1, 1, 0, 1, 1, ""},
{1, 1, 2, 0, 1, 1, ""},
{1, 1, 3, 0, 1, 1, ""},
{1, 1, 0, 0, 1, 1, 1, ""},
{1, 1, 0, 1, 1, 1, 1, ""},
{1, 1, 1, 0, 1, 1, 1, ""},
{1, 1, 2, 0, 1, 1, 1, ""},
{1, 1, 3, 0, 1, 1, 1, ""},
// 3p
{3, 3, 0, 0, unknownPhases, 3, ""},
{3, 3, 0, 1, 1, 1, ""},
{3, 3, 0, 2, 2, 2, ""},
{3, 3, 0, 3, 3, 3, ""},
{3, 3, 1, 0, 1, 1, ""},
{3, 3, 2, 0, 2, 2, ""},
{3, 3, 3, 0, 3, 3, ""},
{3, 3, 0, 0, unknownPhases, 3, 3, ""},
{3, 3, 0, 1, 1, 1, 1, ""},
{3, 3, 0, 2, 2, 2, 2, ""},
{3, 3, 0, 3, 3, 3, 3, ""},
{3, 3, 1, 0, 1, 1, 1, ""},
{3, 3, 2, 0, 2, 2, 2, ""},
{3, 3, 3, 0, 3, 3, 3, ""},
// 1p3p initial
{0, 0, 0, 0, unknownPhases, 3, "du"},
{0, 0, 0, 1, 1, 3, "u"},
{0, 0, 0, 2, 2, 3, "du"},
{0, 0, 0, 3, 3, 3, "du"},
{0, 0, 1, 0, 1, 1, ""},
{0, 0, 2, 0, 2, 2, "du"},
{0, 0, 3, 0, 3, 3, "du"},
{0, 0, 0, 0, unknownPhases, 3, 1, "du"},
{0, 0, 0, 1, 1, 3, 1, "u"},
{0, 0, 0, 2, 2, 3, 1, "du"},
{0, 0, 0, 3, 3, 3, 1, "du"},
{0, 0, 1, 0, 1, 1, 1, ""},
{0, 0, 2, 0, 2, 2, 1, "du"},
{0, 0, 3, 0, 3, 3, 1, "du"},
// 1p3p, 1 currently active
{0, 1, 0, 0, 1, 3, "u"},
{0, 1, 0, 1, 1, 3, "u"},
{0, 1, 0, 0, 1, 3, 1, "u"},
{0, 1, 0, 1, 1, 3, 1, "u"},
// {0, 1, 0, 2, 2,2,"u"}, // 2p active > 1p configured must not happen
// {0, 1, 0, 3, 3,3,"u"}, // 3p active > 1p configured must not happen
{0, 1, 1, 0, 1, 1, ""},
{0, 1, 2, 0, 1, 2, "u"},
{0, 1, 3, 0, 1, 3, "u"},
{0, 1, 1, 0, 1, 1, 1, ""},
{0, 1, 2, 0, 1, 2, 1, "u"},
{0, 1, 3, 0, 1, 3, 1, "u"},
// 1p3p, 3 currently active
{0, 3, 0, 0, unknownPhases, 3, "d"},
{0, 3, 0, 1, 1, 1, ""},
{0, 3, 0, 2, 2, 2, "d"},
{0, 3, 0, 3, 3, 3, "d"},
{0, 3, 1, 0, 1, 1, ""},
{0, 3, 2, 0, 2, 2, "d"},
{0, 3, 3, 0, 3, 3, "d"},
{0, 3, 0, 0, unknownPhases, 3, 1, "d"},
{0, 3, 0, 1, 1, 1, 1, ""},
{0, 3, 0, 2, 2, 2, 1, "d"},
{0, 3, 0, 3, 3, 3, 1, "d"},
{0, 3, 1, 0, 1, 1, 1, ""},
{0, 3, 2, 0, 2, 2, 1, "d"},
{0, 3, 3, 0, 3, 3, 1, "d"},
}

func TestMaxActivePhases(t *testing.T) {
ctrl := gomock.NewController(t)

// 0 is auto, 1/3 are fixed
for _, dflt := range []int{0, 1, 3} {
for _, configured := range []int{0, 1, 3} {
for _, tc := range phaseTests {
// skip invalid configs (free scaling for simple charger)
if dflt == 0 && tc.capable != 0 {
if configured == 0 && tc.capable != 0 {
continue
}

t.Log(dflt, tc)
t.Logf("configured %d %+v", configured, tc)

ctrl := gomock.NewController(t)
plainCharger := api.NewMockCharger(ctrl)

// 1p3p
var phaseCharger *api.MockPhaseSwitcher
if tc.capable == 0 {
phaseCharger = api.NewMockPhaseSwitcher(ctrl)
}

vehicle := api.NewMockVehicle(ctrl)
vehicle.EXPECT().Phases().Return(tc.vehicle).MinTimes(1)

lp := &Loadpoint{
configuredPhases: dflt, // fixed phases or default
configuredPhases: configured, // fixed phases or default
vehicle: vehicle,
phases: tc.physical,
measuredPhases: tc.measuredPhases,
charger: plainCharger,
}

if phaseCharger != nil {
// 1p3p
if tc.capable == 0 {
lp.charger = struct {
*api.MockCharger
*api.MockPhaseSwitcher
}{
plainCharger, phaseCharger,
plainCharger, api.NewMockPhaseSwitcher(ctrl),
}
} else {
}

// restrict scalable charger by config
expectedPhases := tc.maxExpected
if tc.capable == 0 && configured > 0 && configured < tc.maxExpected {
expectedPhases = configured
}

require.Equal(t, expectedPhases, lp.maxActivePhases(), "expected max active phases")
ctrl.Finish()
}
}
}

func TestMinActivePhases(t *testing.T) {
// 0 is auto, 1/3 are fixed
for _, configured := range []int{0, 1, 3} {
for _, tc := range phaseTests {
// skip invalid configs (free scaling for simple charger)
if configured == 0 && tc.capable != 0 {
continue
}

// skip physical config different than configured
if configured != 0 && tc.capable != 0 && configured != tc.physical {
continue
}

t.Logf("configured %d %+v", configured, tc)

ctrl := gomock.NewController(t)
plainCharger := api.NewMockCharger(ctrl)

vehicle := api.NewMockVehicle(ctrl)
vehicle.EXPECT().Phases().Return(tc.vehicle).AnyTimes()

lp := &Loadpoint{
configuredPhases: configured, // fixed phases or default
vehicle: vehicle,
phases: tc.physical,
measuredPhases: tc.measuredPhases,
charger: plainCharger,
}

// 1p3p
if tc.capable == 0 {
lp.charger = struct {
*api.MockCharger
*api.MockPhaseSwitcher
}{
plainCharger,
plainCharger, api.NewMockPhaseSwitcher(ctrl),
}
}

expectedPhases := tc.maxExpected

// restrict scalable charger by config
if tc.capable == 0 && dflt > 0 && dflt < tc.maxExpected {
expectedPhases = dflt
expectedPhases := tc.minExpected
if tc.capable == 0 && configured > 0 && configured < tc.minExpected {
expectedPhases = configured
}

require.Equal(t, expectedPhases, lp.maxActivePhases(), "expected max active phases")
require.Equal(t, expectedPhases, lp.minActivePhases(), "expected min active phases")
ctrl.Finish()
}
}
}
Expand Down

0 comments on commit 0a9d7d6

Please sign in to comment.