diff --git a/core/loadpoint.go b/core/loadpoint.go index 555670f5a..6efca15e4 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -506,11 +506,6 @@ func (lp *Loadpoint) evVehicleConnectHandler() { // soc update reset lp.socUpdated = time.Time{} - // soc update reset on car change - if lp.socEstimator != nil { - lp.socEstimator.Reset() - } - // set default or start detection if !lp.chargerHasFeature(api.IntegratedDevice) { lp.vehicleDefaultOrDetect() @@ -1780,7 +1775,7 @@ func (lp *Loadpoint) publishSocAndRange() { if socEstimator == nil { lp.vehicleSoc = *soc } else { - lp.vehicleSoc, _ = socEstimator.Soc(soc, lp.GetChargedEnergy()) + lp.vehicleSoc = socEstimator.Soc(soc, lp.GetChargedEnergy()) lp.log.DEBUG.Printf("vehicle soc (estimator): %.0f%%", lp.vehicleSoc) } } diff --git a/core/soc/README.md b/core/soc/README.md new file mode 100644 index 000000000..be6fb732e --- /dev/null +++ b/core/soc/README.md @@ -0,0 +1,6 @@ +| fetchedSoc | chargedEnergy | result | +| ---------- | ------------- | --------------------------------------------------- | +| nil | <=0 | 0 | +| nil | value | prevsoc + delta | +| value | <=0 | initialsoc setzen | +| value | value | initialsoc/initialenergy setzen falls nicht gesetzt | diff --git a/core/soc/estimator.go b/core/soc/estimator.go index e6b1e0fe8..d296b655e 100644 --- a/core/soc/estimator.go +++ b/core/soc/estimator.go @@ -25,7 +25,6 @@ type Estimator struct { charger api.Charger vehicle api.Vehicle - capacity float64 // vehicle capacity in Wh cached to simplify testing virtualCapacity float64 // estimated virtual vehicle capacity in Wh vehicleSoc float64 // estimated vehicle Soc initialSoc float64 // first received valid vehicle Soc @@ -43,21 +42,12 @@ func NewEstimator(log *util.Logger, charger api.Charger, vehicle api.Vehicle) *E vehicle: vehicle, } - s.Reset() + s.virtualCapacity = s.vehicle.Capacity() * 1e3 / ChargeEfficiency // initial capacity taking efficiency into account + s.energyPerSocStep = s.virtualCapacity / 100 return s } -// Reset resets the estimation process to default values -func (s *Estimator) Reset() { - s.prevSoc = 0 - s.prevChargedEnergy = 0 - s.initialSoc = 0 - s.capacity = s.vehicle.Capacity() * 1e3 // cache to simplify debugging - s.virtualCapacity = s.capacity / ChargeEfficiency // initial capacity taking efficiency into account - s.energyPerSocStep = s.virtualCapacity / 100 -} - // RemainingChargeDuration returns the estimated remaining duration func (s *Estimator) RemainingChargeDuration(targetSoc, chargePower float64) time.Duration { return remainingChargeDuration(targetSoc, chargePower, s.vehicleSoc, s.virtualCapacity) @@ -99,60 +89,39 @@ func (s *Estimator) RemainingChargeEnergy(targetSoc int) float64 { } // Soc replaces the api.Vehicle.Soc interface to take charged energy into account -func (s *Estimator) Soc(fetchedSoc *float64, chargedEnergy float64) (float64, error) { +func (s *Estimator) Soc(fetchedSoc *float64, chargedEnergy float64) float64 { if fetchedSoc != nil { s.vehicleSoc = *fetchedSoc } else { s.log.WARN.Printf("missing vehicle soc- ignored by estimator") } - if s.virtualCapacity > 0 { - socDelta := s.vehicleSoc - s.prevSoc - energyDelta := max(chargedEnergy, 0) - s.prevChargedEnergy + socDelta := s.vehicleSoc - s.prevSoc + energyDelta := max(chargedEnergy, 0) - s.prevChargedEnergy - if socDelta != 0 || energyDelta < 0 { // soc value change or unexpected energy reset - // compare ChargeState of vehicle and charger - var invalid bool - - if vs, ok := s.vehicle.(api.ChargeState); ok { - ccs, err := s.charger.Status() - if err != nil { - return 0, err - } - vcs, err := vs.Status() - if err != nil { - vcs = ccs // sanitize vehicle errors - } else { - s.log.DEBUG.Printf("vehicle status: %s", vcs) - } - invalid = vcs != ccs - } - - 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 = max(chargedEnergy, 0) - s.prevSoc = s.vehicleSoc - } else { - s.vehicleSoc = min(*fetchedSoc+energyDelta/s.energyPerSocStep, 100) - s.log.DEBUG.Printf("soc estimated: %.2f%% (vehicle: %.2f%%)", s.vehicleSoc, *fetchedSoc) + if socDelta != 0 || energyDelta < 0 { // soc value change or unexpected energy reset + 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 = max(chargedEnergy, 0) + s.prevSoc = s.vehicleSoc + } else { + s.vehicleSoc = min(*fetchedSoc+energyDelta/s.energyPerSocStep, 100) + s.log.DEBUG.Printf("soc estimated: %.2f%% (vehicle: %.2f%%)", s.vehicleSoc, *fetchedSoc) } - return s.vehicleSoc, nil + return s.vehicleSoc } diff --git a/core/soc/estimator_test.go b/core/soc/estimator_test.go index ad6b73d5b..84f3052bf 100644 --- a/core/soc/estimator_test.go +++ b/core/soc/estimator_test.go @@ -71,20 +71,11 @@ func TestSocEstimation(t *testing.T) { for _, tc := range tc { t.Logf("%+v", tc) - soc, err := ce.Soc(&tc.vehicleSoc, tc.chargedEnergy) - if err != nil { - t.Error(err) - } + soc := ce.Soc(&tc.vehicleSoc, tc.chargedEnergy) - // 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 soc/capacity estimate + assert.Equal(t, tc.estimatedSoc, soc, "estimated soc") + assert.Equal(t, tc.virtualCapacity, ce.virtualCapacity, "virtual capacity") // validate duration estimate chargePower := 1e3 @@ -92,9 +83,7 @@ func TestSocEstimation(t *testing.T) { 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) - } + assert.Equal(t, remainingDuration, ce.RemainingChargeDuration(targetSoc, chargePower), "remaining duration") } }