Planner: improve resilience against fluctuations and short charging slots (#5695)

This commit is contained in:
schenlap 2023-01-08 14:36:48 +01:00 • committed by GitHub
parent f5fe313dc0
commit 355fcea14f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 20 deletions

View file

@ -52,6 +52,8 @@ const (
vehicleDetectInterval = 1 * time.Minute
vehicleDetectDuration = 10 * time.Minute
smallSlotDuration = 10 * time.Minute // small planner slot duration we might ignore
guardGracePeriod = 10 * time.Second // allow out of sync during this timespan
)
@ -750,6 +752,7 @@ func (lp *Loadpoint) setPlanActive(active bool) {
// plannerActive checks if charging plan is active
func (lp *Loadpoint) plannerActive() (active bool) {
defer func() {
lp.setPlanActive(active)
lp.publish(targetTimeActive, active)
}()
@ -775,13 +778,16 @@ func (lp *Loadpoint) plannerActive() (active bool) {
requiredDuration = time.Duration(float64(requiredDuration) / soc.ChargeEfficiency)
// anticipate lower charge rates at end of charging curve
if targetSoc >= 80 {
requiredDuration = time.Duration(float64(requiredDuration) / soc.ChargeEfficiency)
var additionalTime time.Duration
if targetSoc >= 90 {
requiredDuration = time.Duration(float64(requiredDuration) / soc.ChargeEfficiency)
}
if targetSoc > 80 && maxPower > 15000 {
additionalTime = 5 * time.Duration(float64(targetSoc-80)/(float64(targetSoc)-lp.vehicleSoc)*float64(requiredDuration)*(1-soc.ChargeEfficiency))
lp.log.DEBUG.Printf("add additional charging time %v for soc > 80%%", additionalTime.Round(time.Minute))
} else if targetSoc > 90 && maxPower > 4000 {
additionalTime = 3 * time.Duration(float64(targetSoc-90)/(float64(targetSoc)-lp.vehicleSoc)*float64(requiredDuration)*(1-soc.ChargeEfficiency))
lp.log.DEBUG.Printf("add additional charging time %v for soc > 90%%", additionalTime.Round(time.Minute))
}
requiredDuration += additionalTime
lp.log.DEBUG.Printf("planning %v until %v at %.0fW", requiredDuration.Round(time.Second), lp.targetTime.Round(time.Second).Local(), maxPower)
@ -794,13 +800,12 @@ func (lp *Loadpoint) plannerActive() (active bool) {
if active {
// ignore short plans if not already active
if !lp.planActive && requiredDuration < 10*time.Minute {
if !lp.planActive && lp.clock.Until(slotEnd) < smallSlotDuration {
lp.log.DEBUG.Printf("plan too short- ignoring remaining %v", requiredDuration.Round(time.Second))
return false
}
// remember last active plan's end time
lp.setPlanActive(true)
lp.planSlotEnd = slotEnd
} else if lp.planActive {
// planner was active (any slot, not necessarily previous slot) and charge goal has not yet been met
@ -809,14 +814,17 @@ func (lp *Loadpoint) plannerActive() (active bool) {
// if the plan did not (entirely) work, we may still be charging beyond plan end- in that case, continue charging
// TODO check when schedule is implemented
lp.log.DEBUG.Println("continuing after target time")
active = true
return true
case lp.clock.Now().Before(lp.planSlotEnd) && !lp.planSlotEnd.IsZero():
// don't stop an already running slot if goal was not met
lp.log.DEBUG.Println("continuing until end of slot")
active = true
return true
case requiredDuration < 30*time.Minute:
lp.log.DEBUG.Printf("continuing for remaining %v", requiredDuration.Round(time.Second))
active = true
return true
case lp.clock.Until(planStart) < smallSlotDuration:
lp.log.DEBUG.Printf("plan will re-start shortly, continuing for remaining %v", lp.clock.Until(planStart).Round(time.Second))
return true
}
}
@ -1869,14 +1877,10 @@ func (lp *Loadpoint) Update(sitePower float64, batteryBuffered bool) {
case mode == api.ModeOff:
err = lp.setLimit(0, true)
lp.resetPhaseTimer()
lp.resetPVTimer()
// immediate charging
case mode == api.ModeNow:
err = lp.fastCharging()
lp.resetPhaseTimer()
lp.resetPVTimer()
// minimum or target charging
case lp.minSocNotReached() || lp.plannerActive():
@ -1885,10 +1889,6 @@ func (lp *Loadpoint) Update(sitePower float64, batteryBuffered bool) {
lp.elapsePVTimer() // let PV mode disable immediately afterwards
case mode == api.ModeMinPV || mode == api.ModePV:
if mode == api.ModeMinPV {
lp.resetPVTimer()
}
targetCurrent := lp.pvMaxCurrent(mode, sitePower, batteryBuffered)
var required bool // false

View file

@ -43,8 +43,15 @@ func (lp *Loadpoint) SetMode(mode api.ChargeMode) {
lp.Mode = mode
lp.publish("mode", mode)
// immediately allow pv mode activity
lp.elapsePVTimer()
// reset timers
switch mode {
case api.ModeNow, api.ModeOff:
lp.resetPhaseTimer()
lp.resetPVTimer()
lp.setPlanActive(false)
case api.ModeMinPV:
lp.resetPVTimer()
}
lp.requestUpdate()
}