Skip to content

Commit

Permalink
Improve estimator behavior (evcc-io#2418)
Browse files Browse the repository at this point in the history
  • Loading branch information
premultiply authored Feb 16, 2022
1 parent bea4b8a commit b3fdef1
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 42 deletions.
39 changes: 26 additions & 13 deletions core/soc/estimator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -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
Expand All @@ -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)
Expand Down
45 changes: 16 additions & 29 deletions core/soc/estimator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,31 @@ 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
vehicleSoC float64
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},
{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, 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++ {
Expand Down Expand Up @@ -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},
Expand All @@ -159,28 +150,24 @@ 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
{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, 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 {
Expand Down

0 comments on commit b3fdef1

Please sign in to comment.