224 lines
6.3 KiB
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()
|
|
}
|
|
}
|