From 455bc3348cbef15e80fcb803e05c1759e95beb72 Mon Sep 17 00:00:00 2001 From: mfuchs1984 <57141790+mfuchs1984@users.noreply.github.com> Date: Thu, 3 Apr 2025 10:52:35 +0200 Subject: [PATCH] Circuit: fix validateCurrent and validatePower (#20183) --- api/api.go | 4 ++-- api/mock.go | 16 +++++++-------- core/circuit/circuit.go | 39 +++++++++++++++++++++++------------- core/circuit/circuit_test.go | 4 ++-- core/loadpoint.go | 4 ++-- 5 files changed, 39 insertions(+), 28 deletions(-) diff --git a/api/api.go b/api/api.go index 605de8959..2082caa82 100644 --- a/api/api.go +++ b/api/api.go @@ -239,8 +239,8 @@ type Circuit interface { SetMaxPower(float64) SetMaxCurrent(float64) Update([]CircuitLoad) error - ValidateCurrent(old, new float64) float64 - ValidatePower(old, new float64) float64 + ValidateCurrent(old, new float64, charging bool) float64 + ValidatePower(old, new float64, charging bool) float64 } // Redactor is an interface to redact sensitive data diff --git a/api/mock.go b/api/mock.go index 7a00eeb17..c7a2315ea 100644 --- a/api/mock.go +++ b/api/mock.go @@ -953,31 +953,31 @@ func (mr *MockCircuitMockRecorder) Update(arg0 any) *gomock.Call { } // ValidateCurrent mocks base method. -func (m *MockCircuit) ValidateCurrent(old, new float64) float64 { +func (m *MockCircuit) ValidateCurrent(old, new float64, charging bool) float64 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateCurrent", old, new) + ret := m.ctrl.Call(m, "ValidateCurrent", old, new, charging) ret0, _ := ret[0].(float64) return ret0 } // ValidateCurrent indicates an expected call of ValidateCurrent. -func (mr *MockCircuitMockRecorder) ValidateCurrent(old, new any) *gomock.Call { +func (mr *MockCircuitMockRecorder) ValidateCurrent(old, new, charging any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCurrent", reflect.TypeOf((*MockCircuit)(nil).ValidateCurrent), old, new) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateCurrent", reflect.TypeOf((*MockCircuit)(nil).ValidateCurrent), old, new, charging) } // ValidatePower mocks base method. -func (m *MockCircuit) ValidatePower(old, new float64) float64 { +func (m *MockCircuit) ValidatePower(old, new float64, charging bool) float64 { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidatePower", old, new) + ret := m.ctrl.Call(m, "ValidatePower", old, new, charging) ret0, _ := ret[0].(float64) return ret0 } // ValidatePower indicates an expected call of ValidatePower. -func (mr *MockCircuitMockRecorder) ValidatePower(old, new any) *gomock.Call { +func (mr *MockCircuitMockRecorder) ValidatePower(old, new, charging any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePower", reflect.TypeOf((*MockCircuit)(nil).ValidatePower), old, new) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatePower", reflect.TypeOf((*MockCircuit)(nil).ValidatePower), old, new, charging) } // Wrap mocks base method. diff --git a/core/circuit/circuit.go b/core/circuit/circuit.go index 051fef087..843bc1fe0 100644 --- a/core/circuit/circuit.go +++ b/core/circuit/circuit.go @@ -320,17 +320,22 @@ func (c *Circuit) GetMaxPhaseCurrent() float64 { } // ValidatePower validates power request -func (c *Circuit) ValidatePower(old, new float64) float64 { +func (c *Circuit) ValidatePower(old, new float64, charging bool) float64 { if maxPower := c.GetMaxPower(); maxPower != 0 { delta := max(0, new-old) potential := maxPower - c.power - if delta > potential { - capped := max(0, old+potential) - c.log.DEBUG.Printf("validate power: %.5gW + (%.5gW -> %.5gW) > %.5gW capped at %.5gW", c.power, old, new, maxPower, capped) - new = capped + if charging { + if delta > potential { + capped := max(0, min(new, min(old+potential, maxPower*1.1))) //allow 10% more than maxPower since some devices may not utilize offered power completely + c.log.DEBUG.Printf("validate power: %.5gW + (%.5gW -> %.5gW) > %.5gW capped at %.5gW", c.power, old, new, maxPower, capped) + new = capped + } else { + c.log.TRACE.Printf("validate power: %.5gW + (%.5gW -> %.5gW) <= %.5gW ok", c.power, old, new, maxPower) + new = min(new, maxPower*1.1) + } } else { - c.log.TRACE.Printf("validate power: %.5gW + (%.5gW -> %.5gW) <= %.5gW ok", c.power, old, new, maxPower) + new = max(0, min(new, potential)) } } @@ -338,21 +343,27 @@ func (c *Circuit) ValidatePower(old, new float64) float64 { return new } - return c.parent.ValidatePower(old, new) + return c.parent.ValidatePower(old, new, charging) } // ValidateCurrent validates current request -func (c *Circuit) ValidateCurrent(old, new float64) float64 { +// ValidateCurrent validates current request +func (c *Circuit) ValidateCurrent(old, new float64, charging bool) float64 { if maxCurrent := c.GetMaxCurrent(); maxCurrent != 0 { delta := max(0, new-old) potential := maxCurrent - c.current - if delta > potential { - capped := max(0, old+potential) - c.log.DEBUG.Printf("validate current: %.3gA + (%.3gA -> %.3gA) > %.3gA capped at %.3gA", c.current, old, new, maxCurrent, capped) - new = capped + if charging { + if delta > potential { + capped := max(0, min(new, min(old+potential, maxCurrent*1.1))) //allow up to 10% more than maxCurrent since some devices may not utilize offered current completely + c.log.DEBUG.Printf("validate current: %.3gA + (%.3gA -> %.3gA) > %.3gA capped at %.3gA", c.current, old, new, maxCurrent, capped) + new = capped + } else { + c.log.TRACE.Printf("validate current: %.3gA + (%.3gA -> %.3gA) <= %.3gA ok", c.current, old, new, maxCurrent) + new = min(new, maxCurrent*1.1) + } } else { - c.log.TRACE.Printf("validate current: %.3gA + (%.3gA -> %.3gA) <= %.3gA ok", c.current, old, new, maxCurrent) + new = max(0, min(new, potential)) } } @@ -360,5 +371,5 @@ func (c *Circuit) ValidateCurrent(old, new float64) float64 { return new } - return c.parent.ValidateCurrent(old, new) + return c.parent.ValidateCurrent(old, new, charging) } diff --git a/core/circuit/circuit_test.go b/core/circuit/circuit_test.go index d7c5175c4..a3aba44d9 100644 --- a/core/circuit/circuit_test.go +++ b/core/circuit/circuit_test.go @@ -82,7 +82,7 @@ func TestCircuitPower(t *testing.T) { 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) + assert.Equal(t, tc.res, c1.ValidatePower(tc.old, tc.new, true), tc) ctrl.Finish() } @@ -124,7 +124,7 @@ func TestCircuitCurrents(t *testing.T) { 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) + assert.Equal(t, tc.res, c1.ValidateCurrent(tc.old, tc.new, true), tc) ctrl.Finish() } diff --git a/core/loadpoint.go b/core/loadpoint.go index fd37526aa..2d2771d6e 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -836,10 +836,10 @@ func (lp *Loadpoint) setLimit(chargeCurrent float64) error { // apply circuit limits if lp.circuit != nil { - currentLimit := lp.circuit.ValidateCurrent(lp.chargeCurrent, chargeCurrent) + currentLimit := lp.circuit.ValidateCurrent(lp.chargeCurrent, chargeCurrent, lp.charging()) activePhases := lp.ActivePhases() - powerLimit := lp.circuit.ValidatePower(lp.chargePower, currentToPower(chargeCurrent, activePhases)) + powerLimit := lp.circuit.ValidatePower(lp.chargePower, currentToPower(chargeCurrent, activePhases), lp.charging()) currentLimitViaPower := powerToCurrent(powerLimit, activePhases) chargeCurrent = lp.roundedCurrent(min(currentLimit, currentLimitViaPower))