From 45e3df9fce5b68d12132b3d4ae4e47d6d0737e46 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 5 May 2024 19:57:30 +0200 Subject: [PATCH] Fix circuits disabling instead of reducing demand (#13768) --- core/circuit.go | 17 ++--- core/circuit_test.go | 164 +++++++++++++++++++++++-------------------- 2 files changed, 95 insertions(+), 86 deletions(-) diff --git a/core/circuit.go b/core/circuit.go index 9c4ee28bf..7feab2d66 100644 --- a/core/circuit.go +++ b/core/circuit.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "math" "sync" "github.com/evcc-io/evcc/api" @@ -176,7 +177,7 @@ func (c *Circuit) updateLoadpoints(loadpoints []api.CircuitLoad) { func (c *Circuit) updateMeters() error { if f, err := c.meter.CurrentPower(); err == nil { // TODO handle negative powers - c.power = f + c.power = math.Abs(f) } else { return fmt.Errorf("circuit power: %w", err) } @@ -184,7 +185,7 @@ func (c *Circuit) updateMeters() error { if phaseMeter, ok := c.meter.(api.PhaseCurrents); ok { if l1, l2, l3, err := phaseMeter.Currents(); err == nil { // TODO handle negative currents - c.current = max(l1, l2, l3) + c.current = max(math.Abs(l1), math.Abs(l2), math.Abs(l3)) } else { return fmt.Errorf("circuit currents: %w", err) } @@ -246,7 +247,7 @@ func (c *Circuit) ValidatePower(old, new float64) float64 { if c.maxPower != 0 { if c.power+delta > c.maxPower { - new = max(0, c.maxPower-c.power) + new = max(0, new-(c.power+delta-c.maxPower)) c.log.DEBUG.Printf("validate power: %gW -> %gW <= %gW at %gW: capped at %gW", old, new, c.maxPower, c.power, new) } else { c.log.TRACE.Printf("validate power: %gW -> %gW <= %gW at %gW: ok", old, new, c.maxPower, c.power) @@ -254,9 +255,9 @@ func (c *Circuit) ValidatePower(old, new float64) float64 { } if c.parent != nil { - res := c.parent.ValidatePower(c.power, new) + res := c.parent.ValidatePower(old, new) if res != new { - c.log.TRACE.Printf("validate power: %gW -> %gW at %gW: capped at %gW", old, new, c.power, new) + c.log.TRACE.Printf("validate power: %gW -> %gW at %gW: capped by parent at %gW", old, new, c.power, res) } return res } @@ -265,12 +266,12 @@ func (c *Circuit) ValidatePower(old, new float64) float64 { } // ValidateCurrent validates current request -func (c *Circuit) ValidateCurrent(old, new float64) (res float64) { +func (c *Circuit) ValidateCurrent(old, new float64) float64 { delta := max(0, new-old) if c.maxCurrent != 0 { if c.current+delta > c.maxCurrent { - new = max(0, c.maxCurrent-c.current) + new = max(0, new-(c.current+delta-c.maxCurrent)) c.log.DEBUG.Printf("validate current: %gA -> %gA <= %gA at %gA: capped at %gA", old, new, c.maxCurrent, c.current, new) } else { c.log.TRACE.Printf("validate current: %gA -> %gA <= %gA at %gA: ok", old, new, c.maxCurrent, c.current) @@ -278,7 +279,7 @@ func (c *Circuit) ValidateCurrent(old, new float64) (res float64) { } if c.parent != nil { - res := c.parent.ValidateCurrent(c.current, new) + res := c.parent.ValidateCurrent(old, new) if res != new { c.log.TRACE.Printf("validate current: %gA -> %gA at %gA: capped by parent at %gA", old, new, c.current, res) } diff --git a/core/circuit_test.go b/core/circuit_test.go index f29e67d74..785ba1546 100644 --- a/core/circuit_test.go +++ b/core/circuit_test.go @@ -5,10 +5,57 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" ) +type circuitTest struct { + // current values for parent, circuit 1, circuit 2 + p, c1, c2 float64 + // old/new demand values and allowed result + old, new, res float64 +} + +func circuitTests() []circuitTest { + return []circuitTest{ + // no load + {0, 0, 0, 0, 0, 0}, // = + {0, 0, 0, 0, 1, 1}, // + + {0, 0, 0, 0, 2, 1}, // + + + // circuit 1 loaded + {0, 1, 0, 0, 0, 0}, // = + {0, 1, 0, 0, 1, 0}, // + + {0, 1, 0, 0, 2, 0}, // + + {0, 1, 0, 1, 1, 1}, // = + {0, 1, 0, 2, 1, 1}, // - + + // circuit 1 overloaded + {0, 2, 0, 0, 0, 0}, // = + {0, 2, 0, 0, 1, 0}, // + + {0, 2, 0, 1, 1, 0}, // = + {0, 2, 0, 2, 2, 1}, // = + {0, 2, 0, 2, 3, 1}, // + + // {0, 2, 0, 2, 1, 1}, // - + + // parent loaded + {1, 0, 0, 0, 0, 0}, // = + {1, 0, 0, 0, 1, 0}, // + + {1, 0, 0, 0, 2, 0}, // + + {1, 0, 0, 1, 1, 1}, // = + {1, 0, 0, 2, 1, 1}, // - + + // parent overloaded + {2, 0, 0, 0, 0, 0}, // = + {2, 0, 0, 0, 1, 0}, // + + {2, 0, 0, 1, 1, 0}, // = + {2, 0, 0, 2, 2, 1}, // = + {2, 0, 0, 2, 3, 1}, // + + // {2, 0, 0, 2, 1, 1}, // - + } +} + func TestCircuitPower(t *testing.T) { log := util.NewLogger("foo") @@ -19,25 +66,7 @@ func TestCircuitPower(t *testing.T) { return c, m } - for _, tc := range []struct { - pm, cm1, cm2 float64 - req, res float64 - }{ - // no load - {0, 0, 0, 0, 0}, - {0, 0, 0, 1, 1}, - {0, 0, 0, 2, 1}, - - // c1 loaded - {0, 1, 0, 0, 0}, - {0, 1, 0, 1, 0}, - {0, 1, 0, 2, 0}, - - // pc loaded - {1, 0, 0, 0, 0}, - {1, 0, 0, 1, 0}, - {1, 0, 0, 2, 0}, - } { + for _, tc := range circuitTests() { ctrl := gomock.NewController(t) pc, pm := circ(t, ctrl, 1) @@ -48,76 +77,55 @@ func TestCircuitPower(t *testing.T) { c2.SetParent(pc) // update meters - pm.EXPECT().CurrentPower().Return(tc.pm, nil) - cm1.EXPECT().CurrentPower().Return(tc.cm1, nil) - cm2.EXPECT().CurrentPower().Return(tc.cm2, nil) + pm.EXPECT().CurrentPower().Return(tc.p, nil) + cm1.EXPECT().CurrentPower().Return(tc.c1, nil) + cm2.EXPECT().CurrentPower().Return(tc.c2, nil) require.NoError(t, pc.Update(nil)) - require.Equal(t, tc.res, c1.ValidatePower(0, tc.req)) + assert.Equal(t, tc.res, c1.ValidatePower(tc.old, tc.new), tc) ctrl.Finish() } } -// func TestCircuitCurrents(t *testing.T) { -// log := util.NewLogger("foo") +func TestCircuitCurrents(t *testing.T) { + log := util.NewLogger("foo") -// type mockMeter struct { -// *api.MockMeter -// *api.MockPhaseCurrents -// } + type combined struct { + *api.MockMeter + *api.MockPhaseCurrents + } + circ := func(t *testing.T, ctrl *gomock.Controller, maxC float64) (*Circuit, combined) { + m := combined{ + api.NewMockMeter(ctrl), + api.NewMockPhaseCurrents(ctrl), + } + c, err := NewCircuit(log, "foo", maxC, 0, m) + require.NoError(t, err) + return c, m + } -// circ := func(t *testing.T, ctrl *gomock.Controller, maxP float64) (*Circuit, *mockMeter) { -// m := api.NewMockMeter(ctrl) -// mc := api.NewMockPhaseCurrents(ctrl) -// mm := &mockMeter{m, mc} -// c, err := NewCircuit(log, 0, maxP, mm) -// require.NoError(t, err) -// return c, mm -// } + for _, tc := range circuitTests() { + ctrl := gomock.NewController(t) -// for _, tc := range []struct { -// pm, cm1, cm2 float64 -// req, res float64 -// }{ -// // no load -// {0, 0, 0, 0, 0}, -// {0, 0, 0, 1, 1}, -// {0, 0, 0, 2, 1}, + pc, pm := circ(t, ctrl, 1) + c1, cm1 := circ(t, ctrl, 1) + c2, cm2 := circ(t, ctrl, 1) -// // c1 loaded -// {0, 1, 0, 0, 0}, -// {0, 1, 0, 1, 0}, -// {0, 1, 0, 2, 0}, + c1.SetParent(pc) + c2.SetParent(pc) -// // pc loaded -// {1, 0, 0, 0, 0}, -// {1, 0, 0, 1, 0}, -// {1, 0, 0, 2, 0}, -// } { -// ctrl := gomock.NewController(t) + // update meters + pm.MockMeter.EXPECT().CurrentPower().AnyTimes().Return(0.0, nil) + cm1.MockMeter.EXPECT().CurrentPower().AnyTimes().Return(0.0, nil) + cm2.MockMeter.EXPECT().CurrentPower().AnyTimes().Return(0.0, nil) + pm.MockPhaseCurrents.EXPECT().Currents().Return(tc.p, tc.p, tc.p, nil) + cm1.MockPhaseCurrents.EXPECT().Currents().Return(tc.c1, tc.c1, tc.c1, nil) + cm2.MockPhaseCurrents.EXPECT().Currents().Return(tc.c2, tc.c2, tc.c2, nil) + require.NoError(t, pc.Update(nil)) -// pc, pm := circ(t, ctrl, 1) -// c1, cm1 := circ(t, ctrl, 1) -// c2, cm2 := circ(t, ctrl, 1) + assert.Equal(t, tc.res, c1.ValidateCurrent(tc.old, tc.new), tc) -// c1.SetParent(pc) -// c2.SetParent(pc) - -// // update meters -// pm.MockMeter.EXPECT().CurrentPower().Return(tc.pm, nil) -// cm1.MockMeter.EXPECT().CurrentPower().Return(tc.cm1, nil) -// cm2.MockMeter.EXPECT().CurrentPower().Return(tc.cm2, nil) - -// // update meters -// pm.MockPhaseCurrents.EXPECT().Currents().Return(tc.pm, tc.pm, tc.pm, nil) -// cm1.MockPhaseCurrents.EXPECT().Currents().Return(tc.cm1, tc.cm1, tc.cm1, nil) -// cm2.MockPhaseCurrents.EXPECT().Currents().Return(tc.cm2, tc.cm2, tc.cm2, nil) -// require.NoError(t, pc.Update(nil)) - -// require.Equal(t, tc.res, c1.ValidatePower(0, tc.req)) -// require.Equal(t, tc.res, c1.ValidateCurrent(0, tc.req)) - -// ctrl.Finish() -// } -// } + ctrl.Finish() + } +}