Improve estimator behavior (#2418)

This commit is contained in:
premultiply 2022-02-16 18:28:16 +01:00 • committed by GitHub
parent bea4b8a55d
commit b3fdef1c0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 42 deletions

View file

@ -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)

View file

@ -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 {