evcc-io/core/site_battery_test.go

224 lines
6.3 KiB
Go

package core
import (
"testing"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
func TestApplyBatteryMode(t *testing.T) {
for _, tc := range []struct {
internal, expected api.BatteryMode
}{
{api.BatteryUnknown, api.BatteryUnknown}, // no change required
{api.BatteryNormal, api.BatteryUnknown}, // no change required
{api.BatteryHold, api.BatteryNormal},
{api.BatteryCharge, api.BatteryNormal},
} {
t.Logf("%+v", tc)
ctrl := gomock.NewController(t)
var bat api.Meter
batCon := api.NewMockBatteryController(ctrl)
bat = &struct {
api.Meter
api.BatteryController
}{
BatteryController: batCon,
}
site := &Site{
log: util.NewLogger("foo"),
batteryMeters: []config.Device[api.Meter]{config.NewStaticDevice(config.Named{}, bat)},
batteryMode: tc.internal,
}
// verify mode applied to battery
if tc.expected != api.BatteryUnknown {
batCon.EXPECT().SetBatteryMode(tc.expected).Times(1)
}
site.updateBatteryMode(false, api.Rate{})
if tc.internal != api.BatteryNormal {
assert.Equal(t, tc.expected, site.batteryMode)
}
ctrl.Finish()
}
}
func TestRequiredExternalBatteryMode(t *testing.T) {
for _, tc := range []struct {
internal, external, new api.BatteryMode
}{
{api.BatteryUnknown, api.BatteryUnknown, api.BatteryUnknown},
{api.BatteryUnknown, api.BatteryNormal, api.BatteryNormal},
{api.BatteryUnknown, api.BatteryCharge, api.BatteryCharge},
{api.BatteryNormal, api.BatteryUnknown, api.BatteryUnknown},
{api.BatteryNormal, api.BatteryNormal, api.BatteryUnknown}, // no change required
{api.BatteryNormal, api.BatteryCharge, api.BatteryCharge},
{api.BatteryCharge, api.BatteryUnknown, api.BatteryNormal},
{api.BatteryCharge, api.BatteryNormal, api.BatteryNormal},
{api.BatteryCharge, api.BatteryCharge, api.BatteryUnknown}, // no change required
} {
t.Logf("%+v", tc)
site := &Site{
log: util.NewLogger("foo"),
batteryMeters: []config.Device[api.Meter]{nil},
}
site.batteryMode = tc.internal
site.batteryModeExternal = tc.external
mode := site.requiredBatteryMode(false, api.Rate{})
assert.Equal(t, tc.new.String(), mode.String(), "internal mode expected %s got %s", tc.new, mode)
}
}
func TestExternalBatteryModeChange(t *testing.T) {
for _, tc := range []struct {
internal, external, expected api.BatteryMode
}{
{api.BatteryUnknown, api.BatteryUnknown, api.BatteryUnknown},
{api.BatteryUnknown, api.BatteryNormal, api.BatteryNormal},
{api.BatteryUnknown, api.BatteryCharge, api.BatteryCharge},
{api.BatteryNormal, api.BatteryUnknown, api.BatteryUnknown},
{api.BatteryNormal, api.BatteryNormal, api.BatteryUnknown},
{api.BatteryNormal, api.BatteryCharge, api.BatteryCharge},
{api.BatteryHold, api.BatteryUnknown, api.BatteryNormal}, // return to normal
{api.BatteryHold, api.BatteryNormal, api.BatteryNormal},
{api.BatteryHold, api.BatteryHold, api.BatteryUnknown},
{api.BatteryCharge, api.BatteryUnknown, api.BatteryNormal}, // return to normal
{api.BatteryCharge, api.BatteryNormal, api.BatteryNormal},
{api.BatteryCharge, api.BatteryCharge, api.BatteryUnknown},
} {
t.Logf("%+v", tc)
ctrl := gomock.NewController(t)
var bat api.Meter
batCon := api.NewMockBatteryController(ctrl)
bat = &struct {
api.Meter
api.BatteryController
}{
BatteryController: batCon,
}
site := &Site{
log: util.NewLogger("foo"),
batteryMeters: []config.Device[api.Meter]{config.NewStaticDevice(config.Named{}, bat)},
batteryMode: tc.internal,
}
// 1. set required external mode
site.SetBatteryModeExternal(tc.external)
assert.Equal(t, site.batteryModeExternal, tc.external, "external mode expected %s got %s", tc.external, site.batteryModeExternal)
assert.Equal(t, site.batteryMode, tc.internal, "internal mode expected unchanged %s got %s", tc.internal, site.batteryMode)
// 2. verify external mode applied to battery
if tc.expected != api.BatteryUnknown {
batCon.EXPECT().SetBatteryMode(tc.expected).Times(1)
}
site.updateBatteryMode(false, api.Rate{})
if !ctrl.Satisfied() {
ctrl.Finish()
}
// 3. verify required external mode only applied once
site.updateBatteryMode(false, api.Rate{})
if !ctrl.Satisfied() {
ctrl.Finish()
}
// 4. verify timer expiry
site.batteryModeExternalTimer = site.batteryModeExternalTimer.Add(-time.Hour)
site.batteryModeWatchdogExpired()
// mode reverted to unknown, timer still active
assert.Equal(t, site.batteryModeExternal, api.BatteryUnknown)
assert.False(t, site.batteryModeExternalTimer.IsZero())
// battery switched back to normal mode
batCon.EXPECT().SetBatteryMode(api.BatteryNormal).Times(1)
site.updateBatteryMode(false, api.Rate{})
// timer disabled
assert.True(t, site.batteryModeExternalTimer.IsZero())
ctrl.Finish()
}
}
func TestForcedBatteryChargeLimits(t *testing.T) {
limit := 80.0
for _, tc := range []struct {
internal, expected api.BatteryMode
soc float64
}{
{api.BatteryUnknown, api.BatteryCharge, 50},
{api.BatteryUnknown, api.BatteryHold, 90},
{api.BatteryNormal, api.BatteryCharge, 50},
{api.BatteryNormal, api.BatteryHold, 90},
{api.BatteryHold, api.BatteryCharge, 50},
{api.BatteryHold, api.BatteryHold, 90}, // TODO make this api.BatteryUnknown
{api.BatteryCharge, api.BatteryUnknown, 50},
{api.BatteryCharge, api.BatteryHold, 90},
} {
t.Logf("%+v", tc)
ctrl := gomock.NewController(t)
var bat api.Meter
batSoc := api.NewMockBattery(ctrl)
batCon := api.NewMockBatteryController(ctrl)
batSocLimit := api.NewMockBatterySocLimiter(ctrl)
bat = &struct {
api.Meter
api.Battery
api.BatteryController
api.BatterySocLimiter
}{
Meter: bat,
Battery: batSoc,
BatteryController: batCon,
BatterySocLimiter: batSocLimit,
}
site := &Site{
log: util.NewLogger("foo"),
batteryMeters: []config.Device[api.Meter]{config.NewStaticDevice(config.Named{}, bat)},
batteryMode: tc.internal,
}
batSoc.EXPECT().Soc().Return(tc.soc, nil).Times(1)
batSocLimit.EXPECT().GetSocLimits().Return(0.0, limit).Times(1)
if tc.expected != api.BatteryUnknown {
batCon.EXPECT().SetBatteryMode(tc.expected).Times(1)
}
site.updateBatteryMode(true, api.Rate{})
ctrl.Finish()
}
}