From b3fdef1c0bb4c6ac1db96ef6a83fc4352d4955d8 Mon Sep 17 00:00:00 2001 From: premultiply Date: Wed, 16 Feb 2022 18:28:16 +0100 Subject: [PATCH] Improve estimator behavior (#2418) --- core/soc/estimator.go | 39 ++++++++++++++++++++++----------- core/soc/estimator_test.go | 45 ++++++++++++++------------------------ 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/core/soc/estimator.go b/core/soc/estimator.go index 55b0929c0..0f09686d6 100644 --- a/core/soc/estimator.go +++ b/core/soc/estimator.go @@ -22,7 +22,9 @@ type Estimator struct { capacity float64 // vehicle capacity in Wh cached to simplify testing virtualCapacity float64 // estimated virtual vehicle capacity in Wh vehicleSoc float64 // estimated vehicle SoC - prevSoC float64 // previous vehicle SoC in % + initialSoc float64 // first received valid vehicle SoC + initialEnergy float64 // energy counter at first valid SoC + prevSoc float64 // previous vehicle SoC in % prevChargedEnergy float64 // previous charged energy in Wh energyPerSocStep float64 // Energy per SoC percent in Wh } @@ -43,8 +45,9 @@ func NewEstimator(log *util.Logger, charger api.Charger, vehicle api.Vehicle, es // Reset resets the estimation process to default values func (s *Estimator) Reset() { - s.prevSoC = 0 + s.prevSoc = 0 s.prevChargedEnergy = 0 + s.initialSoc = 0 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 @@ -112,12 +115,12 @@ func (s *Estimator) SoC(chargedEnergy float64) (float64, error) { if err == nil || !errors.Is(err, api.ErrNotAvailable) { if err != nil { // never received a soc value - if s.prevSoC == 0 { + if s.prevSoc == 0 { return 0, err } // recover from temporary api errors - f = s.prevSoC + f = s.prevSoc s.log.WARN.Printf("vehicle soc (charger): %v (ignored by estimator)", err) } @@ -135,12 +138,12 @@ func (s *Estimator) SoC(chargedEnergy float64) (float64, error) { } // never received a soc value - if s.prevSoC == 0 { + if s.prevSoc == 0 { return 0, err } // recover from temporary api errors - f = s.prevSoC + f = s.prevSoc s.log.WARN.Printf("vehicle soc: %v (ignored by estimator)", err) } @@ -149,7 +152,7 @@ func (s *Estimator) SoC(chargedEnergy float64) (float64, error) { } if s.estimate { - socDelta := s.vehicleSoc - s.prevSoC + socDelta := s.vehicleSoc - s.prevSoc energyDelta := math.Max(chargedEnergy, 0) - s.prevChargedEnergy if socDelta != 0 || energyDelta < 0 { // soc value change or unexpected energy reset @@ -170,16 +173,26 @@ func (s *Estimator) SoC(chargedEnergy float64) (float64, error) { invalid = vcs != ccs } - // calculate gradient, wh per soc % - if !invalid && socDelta > 2 && energyDelta > 0 && s.prevSoC > 0 { - s.energyPerSocStep = energyDelta / socDelta - s.virtualCapacity = s.energyPerSocStep * 100 - s.log.DEBUG.Printf("soc gradient updated: energyPerSocStep: %0.0fWh, virtualCapacity: %0.0fWh", s.energyPerSocStep, s.virtualCapacity) + if !invalid { + if s.initialSoc == 0 { + s.initialSoc = s.vehicleSoc + s.initialEnergy = chargedEnergy + } + + socDiff := s.vehicleSoc - s.initialSoc + energyDiff := chargedEnergy - s.initialEnergy + + // recalculate gradient, wh per soc % + if socDiff > 10 && energyDiff > 0 { + s.energyPerSocStep = energyDiff / socDiff + s.virtualCapacity = s.energyPerSocStep * 100 + s.log.DEBUG.Printf("soc gradient updated: soc: %.1f%%, socDiff: %.1f%%, energyDiff: %.0fWh, energyPerSocStep: %.1fWh, virtualCapacity: %.0fWh", s.vehicleSoc, socDiff, energyDiff, s.energyPerSocStep, s.virtualCapacity) + } } // sample charged energy at soc change, reset energy delta s.prevChargedEnergy = math.Max(chargedEnergy, 0) - s.prevSoC = s.vehicleSoc + s.prevSoc = s.vehicleSoc } else { s.vehicleSoc = math.Min(*fetchedSoC+energyDelta/s.energyPerSocStep, 100) s.log.DEBUG.Printf("soc estimated: %.2f%% (vehicle: %.2f%%)", s.vehicleSoc, *fetchedSoC) diff --git a/core/soc/estimator_test.go b/core/soc/estimator_test.go index 593a606e8..b8c983e97 100644 --- a/core/soc/estimator_test.go +++ b/core/soc/estimator_test.go @@ -44,7 +44,7 @@ func TestSoCEstimation(t *testing.T) { vehicle.EXPECT().Capacity().Return(capacity) ce := NewEstimator(util.NewLogger("foo"), charger, vehicle, true) - ce.vehicleSoc = 20.0 + ce.vehicleSoc = 0.0 tc := []struct { chargedEnergy float64 @@ -52,7 +52,7 @@ func TestSoCEstimation(t *testing.T) { estimatedSoC float64 virtualCapacity float64 }{ - {10, 20.0, 20.0, 10000}, + {0, 0.0, 0.0, 10000}, {0, 20.0, 20.0, 10000}, {123, 20.0, 21.23, 10000}, {1000, 20.0, 30.0, 10000}, @@ -60,23 +60,15 @@ func TestSoCEstimation(t *testing.T) { {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, 10000}, - {8200, 80.0, 80.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, 0.0, 0.0, 10000}, - {1000, 0.0, 10.0, 10000}, + {0, 20.0, 20.0, 10000}, + {1000, 30.0, 30.0, 10000}, } for i := 1; i < 3; i++ { @@ -149,8 +141,7 @@ func TestSoCFromChargerAndVehicleWithErrors(t *testing.T) { }{ // start with SoC from charger and errors {0, 0.0, 0.0, 10000, false, errors.New("some error"), nil}, - {10, 0.0, 20.0, 10000, false, api.ErrMustRetry, nil}, - {10, 20.0, 20.0, 10000, false, nil, 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}, @@ -159,19 +150,15 @@ func TestSoCFromChargerAndVehicleWithErrors(t *testing.T) { {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}, - {4000, 50.0, 50.0, 20000, false, nil, nil}, // 2kWh add 10% -> 20kWh battery - {6000, 60.0, 60.0, 20000, false, nil, nil}, // 2kWh add 10% -> 20kWh battery - {6500, 65.0, 65.0, 10000, false, nil, nil}, - {7000, 65.0, 70.0, 10000, false, nil, nil}, // move to SoC from vehicle - {7000, 0.0, 70.0, 10000, true, api.ErrNotAvailable, errors.New("some error")}, - {7100, 0.0, 71.0, 10000, true, api.ErrNotAvailable, api.ErrMustRetry}, - {7100, 71.0, 71.0, 10000, true, api.ErrNotAvailable, nil}, - {7300, 72.0, 72.0, 10000, true, api.ErrNotAvailable, nil}, - {7400, 0.0, 73.0, 10000, true, api.ErrNotAvailable, errors.New("another error")}, - {7400, 73.0, 73.0, 10000, true, api.ErrNotAvailable, nil}, - {7700, 75.0, 75.0, 10000, true, api.ErrNotAvailable, nil}, - {8200, 80.0, 80.0, 10000, true, api.ErrNotAvailable, nil}, + {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 @@ -179,8 +166,8 @@ func TestSoCFromChargerAndVehicleWithErrors(t *testing.T) { {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, 0.0, 0.0, 10000, false, nil, nil}, - {1000, 0.0, 10.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 {