diff --git a/assets/js/components/VehicleSubline.vue b/assets/js/components/VehicleSubline.vue
index 1596fd632..da526858f 100644
--- a/assets/js/components/VehicleSubline.vue
+++ b/assets/js/components/VehicleSubline.vue
@@ -6,7 +6,7 @@
{{ $t("main.vehicleSubline.mincharge", { soc: minSoC }) }}
-
+
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 424ef1b06..c2d9b536c 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -1390,7 +1390,7 @@ func (lp *LoadPoint) Update(sitePower float64, cheap bool, batteryBuffered bool)
}
// target charging
- case lp.socTimer.DemandActive() && false:
+ case lp.socTimer.DemandActive():
targetCurrent := lp.socTimer.Handle()
err = lp.setLimit(targetCurrent, true)
diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go
index 13d49144e..660a241fa 100644
--- a/core/loadpoint_api.go
+++ b/core/loadpoint_api.go
@@ -55,6 +55,12 @@ func (lp *LoadPoint) GetTargetSoC() int {
return lp.SoC.Target
}
+func (lp *LoadPoint) setTargetSoC(soc int) {
+ lp.SoC.Target = soc
+ lp.socTimer.SoC = soc
+ lp.publish("targetSoC", soc)
+}
+
// SetTargetSoC sets loadpoint charge target soc
func (lp *LoadPoint) SetTargetSoC(soc int) {
lp.Lock()
@@ -64,8 +70,7 @@ func (lp *LoadPoint) SetTargetSoC(soc int) {
// apply immediately
if lp.SoC.Target != soc {
- lp.SoC.Target = soc
- lp.publish("targetSoC", soc)
+ lp.setTargetSoC(soc)
lp.requestUpdate()
}
}
@@ -105,21 +110,20 @@ func (lp *LoadPoint) SetPhases(phases int) error {
}
// SetTargetCharge sets loadpoint charge targetSoC
-func (lp *LoadPoint) SetTargetCharge(finishAt time.Time, targetSoC int) {
+func (lp *LoadPoint) SetTargetCharge(finishAt time.Time, soc int) {
lp.Lock()
defer lp.Unlock()
- lp.log.DEBUG.Printf("set target charge: %d @ %v", targetSoC, finishAt)
+ lp.log.DEBUG.Printf("set target charge: %d @ %v", soc, finishAt)
// apply immediately
- // TODO check reset of targetSoC
- lp.publish("targetTime", finishAt)
- lp.publish("targetSoC", targetSoC)
+ if lp.socTimer.Time != finishAt || lp.SoC.Target != soc {
+ lp.publish("targetTime", finishAt)
+ lp.socTimer.Time = finishAt
- lp.socTimer.Time = finishAt
- lp.socTimer.SoC = targetSoC
-
- lp.requestUpdate()
+ lp.setTargetSoC(soc)
+ lp.requestUpdate()
+ }
}
// RemoteControl sets remote status demand
diff --git a/core/soc/estimator.go b/core/soc/estimator.go
index 748c14718..fe6ae5f47 100644
--- a/core/soc/estimator.go
+++ b/core/soc/estimator.go
@@ -50,6 +50,18 @@ func (s *Estimator) Reset() {
s.energyPerSocStep = s.virtualCapacity / 100
}
+// AssumedChargeDuration estimates charge duration up to targetSoC based on virtual capacity
+func (s *Estimator) AssumedChargeDuration(targetSoC int, chargePower float64) time.Duration {
+ percentRemaining := float64(targetSoC) - s.vehicleSoc
+
+ if percentRemaining <= 0 {
+ return 0
+ }
+
+ whRemaining := percentRemaining / 100 * s.virtualCapacity
+ return time.Duration(float64(time.Hour) * whRemaining / chargePower).Round(time.Second)
+}
+
// RemainingChargeDuration returns the remaining duration estimate based on SoC, target and charge power
func (s *Estimator) RemainingChargeDuration(chargePower float64, targetSoC int) time.Duration {
if chargePower > 0 {
@@ -71,9 +83,7 @@ func (s *Estimator) RemainingChargeDuration(chargePower float64, targetSoC int)
}
}
- // estimate remaining time
- whRemaining := percentRemaining / 100 * s.virtualCapacity
- return time.Duration(float64(time.Hour) * whRemaining / chargePower).Round(time.Second)
+ return s.AssumedChargeDuration(targetSoC, chargePower)
}
return -1
diff --git a/core/soc/timer.go b/core/soc/timer.go
index 6b7fd34b4..29620a6f2 100644
--- a/core/soc/timer.go
+++ b/core/soc/timer.go
@@ -39,9 +39,9 @@ func (lp *Timer) Reset() {
return
}
- lp.current = float64(lp.GetMaxCurrent())
+ lp.current = lp.GetMaxCurrent()
lp.Time = time.Time{}
- lp.SoC = 0
+ lp.active = false
}
// DemandActive calculates remaining charge duration and returns true if charge start is required to achieve target soc in time
@@ -50,9 +50,7 @@ func (lp *Timer) DemandActive() bool {
return false
}
- se := lp.SocEstimator()
- if se == nil {
- lp.log.WARN.Printf("target charging: not possible")
+ if lp.Time.IsZero() {
return false
}
@@ -68,14 +66,28 @@ func (lp *Timer) DemandActive() bool {
power *= lp.current / lp.GetMaxCurrent()
}
+ se := lp.SocEstimator()
+ if se == nil {
+ lp.log.WARN.Printf("target charging: not possible")
+ return false
+ }
+
// time
- remainingDuration := se.RemainingChargeDuration(power, lp.SoC)
+ remainingDuration := se.AssumedChargeDuration(lp.SoC, power)
lp.finishAt = time.Now().Add(remainingDuration).Round(time.Minute)
+ lp.log.DEBUG.Printf("estimated charge duration: %v to %d%% at %.0fW", remainingDuration.Round(time.Minute), lp.SoC, power)
+ if lp.active {
+ lp.log.DEBUG.Printf("projected end: %v", lp.finishAt)
+ lp.log.DEBUG.Printf("desired finish time: %v", lp.Time)
+ } else {
+ lp.log.DEBUG.Printf("projected start: %v", lp.Time.Add(-remainingDuration))
+ }
+
// timer charging is already active- only deactivate once charging has stopped
if lp.active {
if time.Now().After(lp.Time) && lp.GetStatus() != api.StatusC {
- lp.log.TRACE.Printf("target charging: deactivating")
+ lp.log.INFO.Printf("target charging: deactivating")
lp.active = false
}
@@ -85,7 +97,7 @@ func (lp *Timer) DemandActive() bool {
// check if charging need be activated
if lp.active = lp.finishAt.After(lp.Time); lp.active {
lp.current = lp.GetMaxCurrent()
- lp.log.DEBUG.Printf("target charging active for %v: projected %v (%v remaining)", lp.Time, lp.finishAt, remainingDuration.Round(time.Minute))
+ lp.log.INFO.Printf("target charging active for %v: projected %v (%v remaining)", lp.Time, lp.finishAt, remainingDuration.Round(time.Minute))
}
return lp.active
diff --git a/dist/index.html b/dist/index.html
index cbfc5b78a..a5a7887f6 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -1,4 +1,4 @@
-
evcc
\ No newline at end of file
+ };