Loadpoint: simplify estimator (#26956)
This commit is contained in:
parent
766b314efe
commit
ae9a64984f
4 changed files with 39 additions and 80 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
core/soc/README.md
Normal file
6
core/soc/README.md
Normal file
|
|
@ -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 |
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue