diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go index baacd961b..20d17c7a2 100644 --- a/core/loadpoint_plan.go +++ b/core/loadpoint_plan.go @@ -38,23 +38,7 @@ func (lp *Loadpoint) planRequiredDuration(maxPower float64) time.Duration { targetSoc = 100 } - requiredDuration := lp.socEstimator.RemainingChargeDuration(targetSoc, maxPower) - if requiredDuration <= 0 { - return 0 - } - - // anticipate lower charge rates at end of charging curve - var additionalDuration time.Duration - - if targetSoc > 80 && maxPower > 15000 { - additionalDuration = time.Duration(float64(targetSoc-80) / (float64(targetSoc) - lp.vehicleSoc) * float64(requiredDuration)) - lp.log.DEBUG.Printf("add additional charging time %v for soc > 80%%", additionalDuration.Round(time.Minute)) - } else if targetSoc > 90 && maxPower > 4000 { - additionalDuration = time.Duration(float64(targetSoc-90) / (float64(targetSoc) - lp.vehicleSoc) * float64(requiredDuration)) - lp.log.DEBUG.Printf("add additional charging time %v for soc > 90%%", additionalDuration.Round(time.Minute)) - } - - return requiredDuration + additionalDuration + return lp.socEstimator.RemainingChargeDuration(targetSoc, maxPower) } func (lp *Loadpoint) GetPlannerUnit() string { diff --git a/core/soc/estimator.go b/core/soc/estimator.go index 552af9ee7..33b3bb5a6 100644 --- a/core/soc/estimator.go +++ b/core/soc/estimator.go @@ -27,6 +27,9 @@ type Estimator struct { prevSoc float64 // previous vehicle Soc in % prevChargedEnergy float64 // previous charged energy in Wh energyPerSocStep float64 // Energy per Soc percent in Wh + minChargePower float64 // Lowest charge power (just before vehicle stops charging at 100%) + maxChargePower float64 // Highest charge power the battery can handle on any charger + maxChargeSoc float64 // SoC at/after which maxChargePower is degressive } // NewEstimator creates new estimator @@ -51,15 +54,42 @@ func (s *Estimator) Reset() { 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 + s.minChargePower = 1000 // default 1 kW + s.maxChargePower = 50000 // default 50 kW + s.maxChargeSoc = 50 // default 50% } // RemainingChargeDuration returns the estimated remaining duration func (s *Estimator) RemainingChargeDuration(targetSoc int, chargePower float64) time.Duration { - energy := s.RemainingChargeEnergy(targetSoc) * 1e3 / chargePower - if math.IsInf(energy, 0) { - energy = 0 + const minChargeSoc = 100 + + dy := s.minChargePower - s.maxChargePower + dx := minChargeSoc - s.maxChargeSoc + + var rrp float64 = 100 + + if dy > 0 && dx > 0 { + m := dy / dx + b := s.minChargePower - m*minChargeSoc + + // Relativer Reduktionspunkt + rrp = (chargePower - b) / m } - return time.Duration(float64(time.Hour) * energy).Round(time.Second) + + var t1, t2 float64 + + // Zeit von vehicleSoc bis Reduktionspunkt (linear) + if s.vehicleSoc < rrp { + t1 = (math.Min(float64(targetSoc), rrp) - s.vehicleSoc) / minChargeSoc * s.virtualCapacity / chargePower + } + + // Zeit von Reduktionspunkt bis targetSoc (degressiv) + if float64(targetSoc) > rrp { + t2 = (float64(targetSoc) - math.Max(s.vehicleSoc, rrp)) / minChargeSoc * s.virtualCapacity / ((chargePower-s.minChargePower)/2 + s.minChargePower) + + } + + return time.Duration(float64(time.Hour) * (t1 + t2)).Round(time.Second) } // RemainingChargeEnergy returns the remaining charge energy in kWh