diff --git a/api/api.go b/api/api.go index 8568b12cc..7cdcba1e6 100644 --- a/api/api.go +++ b/api/api.go @@ -9,7 +9,7 @@ import ( "time" ) -//go:generate mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,PhaseSwitcher,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery,Tariff,BatteryController +//go:generate mockgen -package api -destination mock.go github.com/evcc-io/evcc/api Charger,ChargeState,CurrentLimiter,PhaseSwitcher,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery,Tariff,BatteryController // ChargeMode is the charge operation mode. Valid values are off, now, minpv and pv type ChargeMode string diff --git a/api/mock.go b/api/mock.go index fbab5af01..0fb7eb354 100644 --- a/api/mock.go +++ b/api/mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,PhaseSwitcher,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery,Tariff,BatteryController) +// Source: github.com/evcc-io/evcc/api (interfaces: Charger,ChargeState,CurrentLimiter,PhaseSwitcher,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery,Tariff,BatteryController) // Package api is a generated GoMock package. package api @@ -129,6 +129,45 @@ func (mr *MockChargeStateMockRecorder) Status() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockChargeState)(nil).Status)) } +// MockCurrentLimiter is a mock of CurrentLimiter interface. +type MockCurrentLimiter struct { + ctrl *gomock.Controller + recorder *MockCurrentLimiterMockRecorder +} + +// MockCurrentLimiterMockRecorder is the mock recorder for MockCurrentLimiter. +type MockCurrentLimiterMockRecorder struct { + mock *MockCurrentLimiter +} + +// NewMockCurrentLimiter creates a new mock instance. +func NewMockCurrentLimiter(ctrl *gomock.Controller) *MockCurrentLimiter { + mock := &MockCurrentLimiter{ctrl: ctrl} + mock.recorder = &MockCurrentLimiterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCurrentLimiter) EXPECT() *MockCurrentLimiterMockRecorder { + return m.recorder +} + +// GetMinMaxCurrent mocks base method. +func (m *MockCurrentLimiter) GetMinMaxCurrent() (float64, float64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMinMaxCurrent") + ret0, _ := ret[0].(float64) + ret1, _ := ret[1].(float64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetMinMaxCurrent indicates an expected call of GetMinMaxCurrent. +func (mr *MockCurrentLimiterMockRecorder) GetMinMaxCurrent() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinMaxCurrent", reflect.TypeOf((*MockCurrentLimiter)(nil).GetMinMaxCurrent)) +} + // MockPhaseSwitcher is a mock of PhaseSwitcher interface. type MockPhaseSwitcher struct { ctrl *gomock.Controller diff --git a/core/loadpoint_effective.go b/core/loadpoint_effective.go index 276947b64..57a441f6d 100644 --- a/core/loadpoint_effective.go +++ b/core/loadpoint_effective.go @@ -60,38 +60,46 @@ func (lp *Loadpoint) SocBasedPlanning() bool { // effectiveMinCurrent returns the effective min current func (lp *Loadpoint) effectiveMinCurrent() float64 { + minCurrent := lp.GetMinCurrent() + if v := lp.GetVehicle(); v != nil { if res, ok := v.OnIdentified().GetMinCurrent(); ok { - return res + minCurrent = max(minCurrent, res) } } if c, ok := lp.charger.(api.CurrentLimiter); ok { if res, _, err := c.GetMinMaxCurrent(); err == nil { - lp.publish(keys.EffectiveMinCurrent, res) - return res + if res > 0 && res < minCurrent { + minCurrent = res + } else { + minCurrent = max(minCurrent, res) + } + lp.publish(keys.EffectiveMinCurrent, minCurrent) } } - return lp.GetMinCurrent() + return minCurrent } // effectiveMaxCurrent returns the effective max current func (lp *Loadpoint) effectiveMaxCurrent() float64 { + maxCurrent := lp.GetMaxCurrent() + if v := lp.GetVehicle(); v != nil { if res, ok := v.OnIdentified().GetMaxCurrent(); ok { - return res + maxCurrent = min(maxCurrent, res) } } if c, ok := lp.charger.(api.CurrentLimiter); ok { if _, res, err := c.GetMinMaxCurrent(); err == nil { - lp.publish(keys.EffectiveMaxCurrent, res) - return res + maxCurrent = min(maxCurrent, res) + lp.publish(keys.EffectiveMaxCurrent, maxCurrent) } } - return lp.GetMaxCurrent() + return maxCurrent } // effectiveLimitSoc returns the effective session limit soc diff --git a/core/loadpoint_effective_test.go b/core/loadpoint_effective_test.go index 888ebca28..060a836fb 100644 --- a/core/loadpoint_effective_test.go +++ b/core/loadpoint_effective_test.go @@ -3,7 +3,9 @@ package core import ( "testing" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) @@ -11,3 +13,54 @@ func TestEffectiveLimitSoc(t *testing.T) { lp := NewLoadpoint(util.NewLogger("foo"), nil) assert.Equal(t, 100, lp.effectiveLimitSoc()) } + +func TestEffectiveMinMaxCurrent(t *testing.T) { + tc := []struct { + chargerMin, chargerMax float64 + vehicleMin, vehicleMax float64 + effectiveMin, effectiveMax float64 + }{ + {0, 0, 0, 0, 6, 16}, + {1, 10, 0, 0, 1, 10}, // charger lower + {10, 20, 0, 0, 10, 16}, // charger higher - max ignored + {0, 0, 1, 10, 6, 10}, // vehicle lower - min ignored + {0, 0, 10, 20, 10, 16}, // vehicle higher - max ignored + {1, 10, 2, 12, 1, 10}, // charger + vehicle lower + {10, 20, 12, 22, 10, 16}, // charger + vehicle higher + } + + for _, tc := range tc { + t.Logf("%+v", tc) + ctrl := gomock.NewController(t) + + lp := NewLoadpoint(util.NewLogger("foo"), nil) + lp.charger = api.NewMockCharger(ctrl) + + if tc.chargerMin+tc.chargerMax > 0 { + currentLimiter := api.NewMockCurrentLimiter(ctrl) + currentLimiter.EXPECT().GetMinMaxCurrent().Return(tc.chargerMin, tc.chargerMax, nil).AnyTimes() + + lp.charger = struct { + api.Charger + api.CurrentLimiter + }{ + Charger: lp.charger, + CurrentLimiter: currentLimiter, + } + } + + if tc.vehicleMin+tc.vehicleMax > 0 { + vehicle := api.NewMockVehicle(ctrl) + ac := api.ActionConfig{ + MinCurrent: tc.vehicleMin, + MaxCurrent: tc.vehicleMax, + } + vehicle.EXPECT().OnIdentified().Return(ac).AnyTimes() + + lp.vehicle = vehicle + } + + assert.Equal(t, tc.effectiveMin, lp.effectiveMinCurrent()) + assert.Equal(t, tc.effectiveMax, lp.effectiveMaxCurrent()) + } +} diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index 89330116a..a08ab6c49 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -88,9 +88,6 @@ func TestNew(t *testing.T) { if lp.phases != 0 { t.Errorf("Phases %v", lp.phases) } - if lp.MinCurrent != minA { - t.Errorf("MinCurrent %v", lp.MinCurrent) - } if lp.MaxCurrent != maxA { t.Errorf("MaxCurrent %v", lp.MaxCurrent) }