package circuit import ( "testing" "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}, // + {0, 0, 0, 1, 1, 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}, // - {0, 1.1, 0, 2, 1, 1}, // - {0, 1.1, 0, 1, 0, 0}, // - // 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}, // - {1.1, 0, 0, 2, 1, 1}, // - {1.1, 0, 0, 1, 0, 0}, // - // negative load {-1, -1, 0, 0, 2, 2}, // + } } func TestCircuitPower(t *testing.T) { log := util.NewLogger("foo") circ := func(t *testing.T, ctrl *gomock.Controller, maxP float64) (*Circuit, *api.MockMeter) { m := api.NewMockMeter(ctrl) c, err := New(log, "foo", 0, maxP, m, 0) require.NoError(t, err) return c, m } for _, tc := range circuitTests() { ctrl := gomock.NewController(t) pc, pm := circ(t, ctrl, 1) c1, cm1 := circ(t, ctrl, 1) c2, cm2 := circ(t, ctrl, 1) c1.setParent(pc) c2.setParent(pc) // update meters 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)) assert.Equal(t, tc.res, c1.ValidatePower(tc.old, tc.new), tc) ctrl.Finish() } } func TestCircuitCurrents(t *testing.T) { log := util.NewLogger("foo") 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 := New(log, "foo", maxC, 0, m, 0) require.NoError(t, err) return c, m } for _, tc := range circuitTests() { ctrl := gomock.NewController(t) pc, pm := circ(t, ctrl, 1) c1, cm1 := circ(t, ctrl, 1) c2, cm2 := circ(t, ctrl, 1) c1.setParent(pc) c2.setParent(pc) // 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)) assert.Equal(t, tc.res, c1.ValidateCurrent(tc.old, tc.new), tc) ctrl.Finish() } } // TestHEMSConsumptionClamp verifies that ValidatePower clamps against the // HEMS consumption limit when one is registered on the root circuit. func TestHEMSConsumptionClamp(t *testing.T) { log := util.NewLogger("foo") for _, tc := range []struct { name string maxPower float64 hemsLimit float64 request float64 want float64 }{ {"no hems, no max", 0, 0, 9000, 9000}, {"no hems, max set", 5000, 0, 9000, 5000}, {"hems below max", 5000, 4200, 9000, 4200}, {"hems above max", 5000, 9000, 9000, 5000}, {"hems but no user max", 0, 4200, 9000, 4200}, } { t.Run(tc.name, func(t *testing.T) { c, err := New(log, "root", 0, tc.maxPower, nil, 0) require.NoError(t, err) if tc.hemsLimit > 0 { ctrl := gomock.NewController(t) hems := api.NewMockHEMS(ctrl) hems.EXPECT().MaxConsumptionPower().Return(&tc.hemsLimit).AnyTimes() c.hems = hems } assert.Equal(t, tc.want, c.ValidatePower(0, tc.request)) }) } }