245 lines
7.8 KiB
Go
245 lines
7.8 KiB
Go
package soc
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/stretchr/testify/assert"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
func TestRemainingChargeDuration(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
charger := api.NewMockCharger(ctrl)
|
|
vehicle := api.NewMockVehicle(ctrl)
|
|
// 8.5 kWh userBatCap => 10 kWh virtualBatCap (at 85% efficiency)
|
|
vehicle.EXPECT().Capacity().Return(float64(8.5))
|
|
|
|
ce := NewEstimator(util.NewLogger("foo"), charger, vehicle, false)
|
|
ce.vehicleSoc = 20.0
|
|
|
|
chargePower := 1000.0
|
|
targetSoc := 80
|
|
|
|
if remaining := ce.RemainingChargeDuration(targetSoc, chargePower); remaining != 6*time.Hour {
|
|
t.Errorf("wrong remaining charge duration: %v", remaining)
|
|
}
|
|
}
|
|
|
|
func TestSocEstimation(t *testing.T) {
|
|
type chargerStruct struct {
|
|
*api.MockCharger
|
|
*api.MockBattery
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
vehicle := api.NewMockVehicle(ctrl)
|
|
charger := &chargerStruct{api.NewMockCharger(ctrl), api.NewMockBattery(ctrl)}
|
|
|
|
// 8.5 kWh user battery capacity is converted to initial value of 10 kWh virtual capacity (at 85% efficiency)
|
|
var capacity float64 = 8.5
|
|
vehicle.EXPECT().Capacity().Return(capacity)
|
|
|
|
ce := NewEstimator(util.NewLogger("foo"), charger, vehicle, true)
|
|
ce.vehicleSoc = 0.0
|
|
|
|
tc := []struct {
|
|
chargedEnergy float64
|
|
vehicleSoc float64
|
|
estimatedSoc float64
|
|
virtualCapacity float64
|
|
}{
|
|
{0, 0.0, 0.0, 10000},
|
|
{0, 20.0, 20.0, 10000},
|
|
{123, 20.0, 21.23, 10000},
|
|
{1000, 20.0, 30.0, 10000},
|
|
{1100, 31.0, 31.0, 10000},
|
|
{1200, 32.0, 32.0, 10000},
|
|
{1900, 39.0, 39.0, 10000},
|
|
{2000, 40.0, 40.0, 10000},
|
|
{6000, 80.0, 80.0, 10000},
|
|
{0, 25.0, 25.0, 10000},
|
|
{2500, 25.0, 50.0, 10000},
|
|
{0, 50.0, 50.0, 10000}, // -10000
|
|
{4990, 50.0, 99.9, 10000},
|
|
{5000, 50.0, 100.0, 10000},
|
|
{5001, 50.0, 100.0, 10000},
|
|
{0, 20.0, 20.0, 10000},
|
|
{1000, 30.0, 30.0, 10000},
|
|
}
|
|
|
|
for i := 1; i < 3; i++ {
|
|
useVehicleSoc := true
|
|
if i == 2 {
|
|
useVehicleSoc = false
|
|
}
|
|
for _, tc := range tc {
|
|
t.Logf("%+v", tc)
|
|
if useVehicleSoc {
|
|
charger.MockBattery.EXPECT().Soc().Return(tc.vehicleSoc, nil)
|
|
} else {
|
|
charger.MockBattery.EXPECT().Soc().Return(0.0, api.ErrNotAvailable)
|
|
vehicle.EXPECT().Soc().Return(tc.vehicleSoc, nil)
|
|
}
|
|
|
|
soc, err := ce.Soc(tc.chargedEnergy)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
// validate soc estimate
|
|
if tc.estimatedSoc != soc {
|
|
t.Errorf("expected estimated soc: %g, got: %g", tc.estimatedSoc, soc)
|
|
}
|
|
|
|
// validate capacity estimate
|
|
if tc.virtualCapacity != ce.virtualCapacity {
|
|
t.Errorf("expected virtual capacity: %v, got: %v", tc.virtualCapacity, ce.virtualCapacity)
|
|
}
|
|
|
|
// validate duration estimate
|
|
chargePower := 1e3
|
|
targetSoc := 100
|
|
remainingHours := (float64(targetSoc) - soc) / 100 * tc.virtualCapacity / chargePower
|
|
remainingDuration := time.Duration(float64(time.Hour) * remainingHours).Round(time.Second)
|
|
|
|
if rm := ce.RemainingChargeDuration(targetSoc, chargePower); rm != remainingDuration {
|
|
t.Errorf("expected estimated duration: %v, got: %v", remainingDuration, rm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSocFromChargerAndVehicleWithErrors(t *testing.T) {
|
|
type chargerStruct struct {
|
|
*api.MockCharger
|
|
*api.MockBattery
|
|
}
|
|
|
|
ctrl := gomock.NewController(t)
|
|
vehicle := api.NewMockVehicle(ctrl)
|
|
charger := &chargerStruct{api.NewMockCharger(ctrl), api.NewMockBattery(ctrl)}
|
|
|
|
// 8.5 kWh user battery capacity is converted to initial value of 10 kWh virtual capacity (at 85% efficiency)
|
|
var capacity float64 = 8.5
|
|
vehicle.EXPECT().Capacity().Return(capacity)
|
|
|
|
ce := NewEstimator(util.NewLogger("foo"), charger, vehicle, true)
|
|
ce.vehicleSoc = 20.0
|
|
|
|
tc := []struct {
|
|
chargedEnergy float64
|
|
vehicleSoc float64
|
|
estimatedSoc float64
|
|
virtualCapacity float64
|
|
expectVehicle bool
|
|
chargerError error
|
|
vehicleError error
|
|
}{
|
|
// start with Soc from charger and errors
|
|
{0, 0.0, 0.0, 10000, false, errors.New("some error"), nil},
|
|
{0, 0.0, 20.0, 10000, false, api.ErrMustRetry, nil},
|
|
{0, 20.0, 20.0, 10000, false, nil, nil},
|
|
{123, 20.0, 21.23, 10000, false, nil, nil},
|
|
{123, 0.0, 21.23, 10000, false, errors.New("another error"), nil},
|
|
{1000, 20.0, 30.0, 10000, false, nil, nil},
|
|
{1100, 31.0, 31.0, 10000, false, nil, nil},
|
|
{1200, 32.0, 32.0, 10000, false, nil, nil},
|
|
{1900, 39.0, 39.0, 10000, false, nil, nil},
|
|
{2000, 40.0, 40.0, 10000, false, nil, nil},
|
|
// move to Soc from vehicle
|
|
{3000, 0.0, 50.0, 10000, true, api.ErrNotAvailable, errors.New("some error")},
|
|
{3100, 0.0, 51.0, 10000, true, api.ErrNotAvailable, api.ErrMustRetry},
|
|
{5100, 71.0, 71.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{5200, 72.0, 72.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{5300, 0.0, 73.0, 10000, true, api.ErrNotAvailable, errors.New("another error")},
|
|
{5300, 73.0, 73.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{5500, 75.0, 75.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{6000, 80.0, 80.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{0, 25.0, 25.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{2500, 25.0, 50.0, 10000, true, api.ErrNotAvailable, nil},
|
|
{0, 50.0, 50.0, 10000, true, api.ErrNotAvailable, nil}, // -10000
|
|
{4990, 50.0, 99.9, 10000, true, api.ErrNotAvailable, nil},
|
|
{5000, 50.0, 100.0, 10000, true, api.ErrNotAvailable, nil},
|
|
// back to Soc from charger
|
|
{5001, 50.0, 100.0, 10000, false, nil, nil},
|
|
{0, 20.0, 20.0, 10000, false, nil, nil},
|
|
{1000, 30.0, 30.0, 10000, false, nil, nil},
|
|
}
|
|
|
|
for _, tc := range tc {
|
|
t.Logf("%+v", tc)
|
|
charger.MockBattery.EXPECT().Soc().Return(tc.vehicleSoc, tc.chargerError)
|
|
if tc.expectVehicle {
|
|
vehicle.EXPECT().Soc().Return(tc.vehicleSoc, tc.vehicleError)
|
|
}
|
|
|
|
soc, err := ce.Soc(tc.chargedEnergy)
|
|
if err != nil {
|
|
if (!tc.expectVehicle && err != tc.chargerError) || (tc.expectVehicle && err != tc.vehicleError) {
|
|
t.Error(err)
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// validate soc estimate
|
|
if tc.estimatedSoc != soc {
|
|
t.Errorf("expected estimated soc: %g, got: %g", tc.estimatedSoc, soc)
|
|
}
|
|
|
|
// validate capacity estimate
|
|
if tc.virtualCapacity != ce.virtualCapacity {
|
|
t.Errorf("expected virtual capacity: %v, got: %v", tc.virtualCapacity, ce.virtualCapacity)
|
|
}
|
|
|
|
// validate duration estimate
|
|
chargePower := 1e3
|
|
targetSoc := 100
|
|
remainingHours := (float64(targetSoc) - soc) / 100 * tc.virtualCapacity / chargePower
|
|
remainingDuration := time.Duration(float64(time.Hour) * remainingHours).Round(time.Second)
|
|
|
|
if rm := ce.RemainingChargeDuration(targetSoc, chargePower); rm != remainingDuration {
|
|
t.Errorf("expected estimated duration: %v, got: %v", remainingDuration, rm)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestImprovedEstimatorRemainingChargeDuration(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
charger := api.NewMockCharger(ctrl)
|
|
vehicle := api.NewMockVehicle(ctrl)
|
|
|
|
// https://github.com/evcc-io/evcc/pull/7510#issuecomment-1512688548
|
|
// Updated for 85% charge efficiency (previously 90%)
|
|
tc := []struct {
|
|
capacity float64
|
|
soc float64
|
|
targetsoc int
|
|
chargePower float64
|
|
duration time.Duration
|
|
}{
|
|
{0.75, 10, 60, 300, 1*time.Hour + 28*time.Minute + 14*time.Second},
|
|
{0.75, 50, 100, 300, 1*time.Hour + 28*time.Minute + 14*time.Second},
|
|
{17, 10, 60, 7 * 1e3, 1*time.Hour + 25*time.Minute + 43*time.Second},
|
|
{17, 50, 100, 7 * 1e3, 1*time.Hour + 33*time.Minute + 35*time.Second},
|
|
{50, 10, 60, 11 * 1e3, 2*time.Hour + 40*time.Minute + 26*time.Second},
|
|
{50, 50, 100, 11 * 1e3, 3*time.Hour + 7*time.Minute + 43*time.Second},
|
|
{80, 10, 60, 22 * 1e3, 2*time.Hour + 8*time.Minute + 21*time.Second},
|
|
{80, 50, 100, 22 * 1e3, 2*time.Hour + 58*time.Minute + 34*time.Second},
|
|
}
|
|
|
|
for _, tc := range tc {
|
|
t.Log(tc)
|
|
|
|
vehicle.EXPECT().Capacity().Return(tc.capacity)
|
|
|
|
ce := NewEstimator(util.NewLogger("foo"), charger, vehicle, false)
|
|
ce.vehicleSoc = tc.soc
|
|
|
|
assert.Equal(t, tc.duration, ce.RemainingChargeDuration(tc.targetsoc, tc.chargePower))
|
|
}
|
|
}
|