Fix circuits disabling instead of reducing demand (#13768)

This commit is contained in:
andig 2024-05-05 19:57:30 +02:00 • committed by GitHub
parent 1a4e213ba9
commit 45e3df9fce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 86 deletions

View file

@ -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)
}

View file

@ -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()
}
}