From 46411cc6020f9276569c17035988321e5d339336 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 26 Sep 2020 12:35:38 +0200 Subject: [PATCH] Fix virtual capacity calculation and add tests (#360) --- core/wrapper/socestimator.go | 8 +-- core/wrapper/socestimator_test.go | 84 +++++++++++++++++++------------ 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/core/wrapper/socestimator.go b/core/wrapper/socestimator.go index d8e50ba78..62a8c8c01 100644 --- a/core/wrapper/socestimator.go +++ b/core/wrapper/socestimator.go @@ -8,6 +8,8 @@ import ( "github.com/andig/evcc/util" ) +const chargeEfficiency = 0.9 // assume charge 90% efficiency + // SocEstimator provides vehicle soc and charge duration // Vehicle SoC can be estimated to provide more granularity type SocEstimator struct { @@ -40,8 +42,8 @@ func NewSocEstimator(log *util.Logger, vehicle api.Vehicle, estimate bool) *SocE func (s *SocEstimator) Reset() { s.prevSoC = 0 s.prevChargedEnergy = 0 - s.capacity = float64(s.vehicle.Capacity()) * 1e3 // cache to simplify debugging - s.virtualCapacity = s.capacity / 0.9 // assume charge 90% efficiency + s.capacity = float64(s.vehicle.Capacity()) * 1e3 // cache to simplify debugging + s.virtualCapacity = s.capacity / chargeEfficiency // initial capacity taking efficiency into account s.energyPerSocStep = s.virtualCapacity / 100 } @@ -96,7 +98,7 @@ func (s *SocEstimator) SoC(chargedEnergy float64) (float64, error) { // calculate gradient, wh per soc % if socDelta > 1 && energyDelta > 0 && s.prevSoC > 0 { s.energyPerSocStep = energyDelta / socDelta - s.virtualCapacity = s.energyPerSocStep * 100 / 1e3 + s.virtualCapacity = s.energyPerSocStep * 100 s.log.TRACE.Printf("soc gradient updated: energyPerSocStep: %0.0fWh, virtualCapacity: %0.1fkWh", s.energyPerSocStep, s.virtualCapacity) } diff --git a/core/wrapper/socestimator_test.go b/core/wrapper/socestimator_test.go index 7a4616966..2e61de649 100644 --- a/core/wrapper/socestimator_test.go +++ b/core/wrapper/socestimator_test.go @@ -12,7 +12,7 @@ import ( func TestRemainingChargeDuration(t *testing.T) { ctrl := gomock.NewController(t) vehicle := mock.NewMockVehicle(ctrl) - //9 kWh userBatCap => 10 kWh virtualBatCap + //9 kWh userBatCap => 10 kWh virtualBatCap vehicle.EXPECT().Capacity().Return(int64(9)) ce := NewSocEstimator(util.NewLogger("foo"), vehicle, false) @@ -29,42 +29,45 @@ func TestRemainingChargeDuration(t *testing.T) { func TestSoCEstimation(t *testing.T) { ctrl := gomock.NewController(t) vehicle := mock.NewMockVehicle(ctrl) - //9 kWh userBatCap => 10 kWh virtualBatCap - vehicle.EXPECT().Capacity().Return(int64(9)) + + // 9 kWh user battery capacity is converted to initial value of 10 kWh virtual capacity + var capacity int64 = 9 + vehicle.EXPECT().Capacity().Return(capacity) ce := NewSocEstimator(util.NewLogger("foo"), vehicle, true) ce.socCharge = 20.0 tc := []struct { - chargedEnergy float64 - vehicleSoC float64 - estimatedSoC float64 + chargedEnergy float64 + vehicleSoC float64 + estimatedSoC float64 + virtualCapacity float64 }{ - {10, 20.0, 20.0}, - {0, 20.0, 20.0}, - {123, 20.0, 21.23}, - {1000, 20.0, 30.0}, - {1100, 31.0, 31.0}, - {1200, 32.0, 32.0}, - {1900, 39.0, 39.0}, - {2000, 40.0, 40.0}, - {4000, 50.0, 50.0}, - {6000, 60.0, 60.0}, - {6500, 65.0, 65.0}, - {7000, 65.0, 70.0}, - {7100, 71.0, 71.0}, - {7300, 72.0, 72.0}, - {7400, 73.0, 73.0}, - {7700, 75.0, 75.0}, - {8200, 80.0, 80.0}, - {0, 25.0, 25.0}, - {2500, 25.0, 50.0}, - {0, 50.0, 50.0}, // -10000 - {4990, 50.0, 99.9}, - {5000, 50.0, 100.0}, - {5001, 50.0, 100.0}, - {0, 0.0, 0.0}, - {1000, 0.0, 10.0}, + {10, 20.0, 20.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}, + {4000, 50.0, 50.0, 20000}, // 2kWh add 10% -> 20kWh battery + {6000, 60.0, 60.0, 20000}, // 2kWh add 10% -> 20kWh battery + {6500, 65.0, 65.0, 10000}, + {7000, 65.0, 70.0, 10000}, + {7100, 71.0, 71.0, 10000}, + {7300, 72.0, 72.0, 10000}, + {7400, 73.0, 73.0, 10000}, + {7700, 75.0, 75.0, 15000}, // 0.3kWh add 2% -> 15kWh battery + {8200, 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, 0.0, 0.0, 10000}, + {1000, 0.0, 10.0, 10000}, } for _, tc := range tc { @@ -75,10 +78,25 @@ func TestSoCEstimation(t *testing.T) { if err != nil { t.Error(err) } - t.Logf("%+v", ce) + // validate soc estimate if tc.estimatedSoC != soc { - t.Errorf("expected: %g, got: %g", 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(chargePower, targetSoC); rm != remainingDuration { + t.Errorf("expected estimated duration: %v, got: %v", remainingDuration, rm) } } }