evcc-io/core/loadpoint_test.go
2020-04-26 15:40:41 +02:00

315 lines
7.1 KiB
Go

package core
import (
"reflect"
"testing"
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/meter"
"github.com/andig/evcc/mock"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/push"
"github.com/benbjohnson/clock"
"github.com/golang/mock/gomock"
)
const (
lpMinCurrent int64 = 6
lpMaxCurrent int64 = 16
)
func TestNew(t *testing.T) {
lp := NewLoadPoint()
if lp.Mode != api.ModeOff {
t.Errorf("Mode %v", lp.Mode)
}
if lp.Phases != 1 {
t.Errorf("Phases %v", lp.Phases)
}
if lp.MinCurrent != lpMinCurrent {
t.Errorf("MinCurrent %v", lp.MinCurrent)
}
if lp.MaxCurrent != lpMaxCurrent {
t.Errorf("MaxCurrent %v", lp.MaxCurrent)
}
if lp.Sensitivity != 10 {
t.Errorf("Sensitivity %v", lp.Sensitivity)
}
if lp.status != api.StatusNone {
t.Errorf("status %v", lp.status)
}
if lp.enabled {
t.Errorf("enabled %v", lp.enabled)
}
if lp.charging {
t.Errorf("charging %v", lp.charging)
}
if lp.targetCurrent != 0 {
t.Errorf("targetCurrent %v", lp.targetCurrent)
}
}
func newLoadPoint(charger api.Charger, pv, gm, cm api.Meter) *LoadPoint {
lp := NewLoadPoint()
lp.clock = clock.NewMock()
lp.clock.(*clock.Mock).Add(time.Hour)
lp.charger = charger
lp.pvMeter = pv
lp.gridMeter = gm
// prevent assigning a nil pointer sake of
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I/discussion
if !(cm == nil || reflect.ValueOf(cm).IsNil()) {
lp.chargeMeter = cm
}
uiChan := make(chan Param)
notificationChan := make(chan push.Event)
lp.Prepare(uiChan, notificationChan)
go func() {
for {
select {
case <-uiChan:
case <-notificationChan:
}
}
}()
return lp
}
func newEnvironment(t *testing.T, ctrl *gomock.Controller, pm, gm, cm api.Meter) (*LoadPoint, *mock.MockCharger) {
wb := mock.NewMockCharger(ctrl)
wb.EXPECT().Enabled().Return(true, nil) // initial alignment with wb
wb.EXPECT().MaxCurrent(lpMinCurrent) // initial alignment with wb
lp := newLoadPoint(wb, pm, gm, cm)
if !lp.enabled {
t.Errorf("enabled %v", lp.enabled)
}
if lp.guardUpdated != lp.clock.Now() {
t.Errorf("guardUpdated %v", lp.guardUpdated)
}
return lp, wb
}
func TestMeterConfigurations(t *testing.T) {
tc := []struct {
gm, cm, pm bool
}{
// {false, false, false}, // no meter
// {false, true, false}, // cm only
{false, false, true}, // pm only
{true, false, false}, // gm only
{true, true, false}, // gm + cm
{true, false, true}, // gm + pm
{true, true, true}, // gm + cm + pm
}
fg := provider.FloatGetter(func() (float64, error) {
return 1, nil
})
for _, tc := range tc {
t.Logf("gm: %+v cm: %v pm: %v", tc.gm, tc.cm, tc.pm)
var gm, pm, cm api.Meter
if tc.gm {
gm = meter.NewConfigurable(fg)
}
if tc.cm {
cm = meter.NewConfigurable(fg)
}
if tc.pm {
pm = meter.NewConfigurable(fg)
}
ctrl := gomock.NewController(t)
lp, wb := newEnvironment(t, ctrl, pm, gm, cm)
wb.EXPECT().Status().Return(api.StatusA, nil)
lp.update()
}
}
func TestInitialUpdate(t *testing.T) {
tc := []struct {
status api.ChargeStatus
mode api.ChargeMode
}{
{status: api.StatusA, mode: api.ModeOff},
{status: api.StatusA, mode: api.ModeNow},
{status: api.StatusA, mode: api.ModeMinPV},
{status: api.StatusA, mode: api.ModePV},
{status: api.StatusB, mode: api.ModeOff},
{status: api.StatusB, mode: api.ModeNow},
{status: api.StatusB, mode: api.ModeMinPV},
{status: api.StatusB, mode: api.ModePV},
{status: api.StatusC, mode: api.ModeOff},
{status: api.StatusC, mode: api.ModeNow},
{status: api.StatusC, mode: api.ModeMinPV},
{status: api.StatusC, mode: api.ModePV},
}
for _, tc := range tc {
t.Logf("%+v\n", tc)
ctrl := gomock.NewController(t)
pm := mock.NewMockMeter(ctrl)
gm := mock.NewMockMeter(ctrl)
cm := mock.NewMockMeter(ctrl)
// cm = nil
lp, wb := newEnvironment(t, ctrl, pm, gm, cm)
lp.Mode = tc.mode
wb.EXPECT().Status().Return(tc.status, nil)
// values are relevant for PV case
minPower := float64(lpMinCurrent) * lp.Voltage
pm.EXPECT().CurrentPower().Return(minPower, nil)
gm.EXPECT().CurrentPower().Return(float64(0), nil)
if cm != nil {
cm.EXPECT().CurrentPower().Return(minPower, nil)
}
// disable if not connected
if tc.status != api.StatusA && tc.mode == api.ModeOff {
wb.EXPECT().Enable(false)
}
// power up if now
if tc.status != api.StatusA && tc.mode == api.ModeNow {
wb.EXPECT().MaxCurrent(lpMaxCurrent)
}
lp.update()
// max current if connected & mode now
if tc.status != api.StatusA && tc.mode == api.ModeNow {
if lp.targetCurrent != lpMaxCurrent {
t.Errorf("targetCurrent %v", lp.targetCurrent)
}
}
// min current in first cycle
if tc.mode != api.ModeNow {
if lp.targetCurrent != lpMinCurrent {
t.Errorf("targetCurrent %v", lp.targetCurrent)
}
}
// status c means charging
if lp.charging != (tc.status == api.StatusC) {
t.Errorf("charging %v", lp.charging)
}
ctrl.Finish()
}
}
func TestImmediateOnOff(t *testing.T) {
tc := []struct {
status api.ChargeStatus
mode api.ChargeMode
}{
{status: api.StatusC, mode: api.ModePV},
}
for _, tc := range tc {
t.Logf("%+v\n", tc)
ctrl := gomock.NewController(t)
pm := mock.NewMockMeter(ctrl)
gm := mock.NewMockMeter(ctrl)
cm := mock.NewMockMeter(ctrl)
// cm = nil
lp, wb := newEnvironment(t, ctrl, pm, gm, cm)
lp.Mode = tc.mode
// -- round 1
wb.EXPECT().Status().Return(tc.status, nil)
// values are relevant for PV case
minPower := float64(lpMinCurrent) * lp.Voltage * float64(lp.Phases)
pm.EXPECT().CurrentPower().Return(minPower, nil)
gm.EXPECT().CurrentPower().Return(0.0, nil)
if cm != nil {
cm.EXPECT().CurrentPower().Return(minPower, nil)
}
// disable if not connected
if tc.status != api.StatusA && tc.mode == api.ModeOff {
wb.EXPECT().Enable(false)
}
// power up if now
if tc.status != api.StatusA && tc.mode == api.ModeNow {
wb.EXPECT().MaxCurrent(lpMaxCurrent)
}
lp.update()
// max current if connected & mode now
if tc.status != api.StatusA && tc.mode == api.ModeNow {
if lp.targetCurrent != lpMaxCurrent {
t.Errorf("targetCurrent %v", lp.targetCurrent)
}
}
// min current in first cycle
if tc.mode != api.ModeNow {
if lp.targetCurrent != lpMinCurrent {
t.Errorf("targetCurrent %v", lp.targetCurrent)
}
}
// status c means charging
if lp.charging != (tc.status == api.StatusC) {
t.Errorf("charging %v", lp.charging)
}
// -- round 2
wb.EXPECT().Status().Return(tc.status, nil)
pm.EXPECT().CurrentPower().Return(minPower, nil)
gm.EXPECT().CurrentPower().Return(-2*minPower, nil)
if cm != nil {
cm.EXPECT().CurrentPower().Return(1.0, nil)
}
wb.EXPECT().MaxCurrent(2 * lpMinCurrent)
lp.update()
// -- round 3
t.Logf("%+v - 3 (status: %v, enabled: %v, current %d)\n", tc, lp.status, lp.enabled, lp.targetCurrent)
wb.EXPECT().Status().Return(tc.status, nil)
pm.EXPECT().CurrentPower().Return(minPower, nil)
gm.EXPECT().CurrentPower().Return(-2*minPower, nil)
if cm != nil {
cm.EXPECT().CurrentPower().Return(1.0, nil)
}
wb.EXPECT().MaxCurrent(lpMinCurrent)
lp.SetMode(api.ModeOff)
lp.update()
ctrl.Finish()
}
}