101 lines
2.2 KiB
Go
101 lines
2.2 KiB
Go
package soc
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/util"
|
|
)
|
|
|
|
const (
|
|
deviation = 30 * time.Minute
|
|
)
|
|
|
|
// Timer is the target charging handler
|
|
type Timer struct {
|
|
Adapter
|
|
log *util.Logger
|
|
maxCurrent float64
|
|
current float64
|
|
SoC int
|
|
Time time.Time
|
|
finishAt time.Time
|
|
chargeRequired bool
|
|
}
|
|
|
|
// NewTimer creates a Timer
|
|
func NewTimer(log *util.Logger, adapter Adapter, maxCurrent float64) *Timer {
|
|
lp := &Timer{
|
|
log: log,
|
|
Adapter: adapter,
|
|
maxCurrent: maxCurrent,
|
|
}
|
|
|
|
return lp
|
|
}
|
|
|
|
// Reset resets the target charging request
|
|
func (lp *Timer) Reset() {
|
|
if lp == nil {
|
|
return
|
|
}
|
|
|
|
lp.current = float64(lp.maxCurrent)
|
|
lp.Time = time.Time{}
|
|
lp.SoC = 0
|
|
}
|
|
|
|
// StartRequired calculates remaining charge duration and returns true if charge start is required to achieve target soc in time
|
|
func (lp *Timer) StartRequired() bool {
|
|
if lp == nil {
|
|
return false
|
|
}
|
|
|
|
se := lp.SocEstimator()
|
|
if !lp.active() || se == nil {
|
|
return false
|
|
}
|
|
|
|
power := float64(lp.ActivePhases()) * lp.maxCurrent * lp.Voltage()
|
|
|
|
// time
|
|
remainingDuration := se.RemainingChargeDuration(power, lp.SoC)
|
|
lp.finishAt = time.Now().Add(remainingDuration).Round(time.Minute)
|
|
lp.log.DEBUG.Printf("target charging active for %v: projected %v (%v remaining)", lp.Time, lp.finishAt, remainingDuration.Round(time.Minute))
|
|
|
|
lp.chargeRequired = lp.finishAt.After(lp.Time)
|
|
lp.Publish("timerActive", lp.chargeRequired)
|
|
|
|
return lp.chargeRequired
|
|
}
|
|
|
|
// active returns true if there is an active target charging request
|
|
func (lp *Timer) active() bool {
|
|
inactive := lp.Time.IsZero() || lp.Time.Before(time.Now())
|
|
lp.Publish("timerSet", !inactive)
|
|
|
|
// reset active
|
|
if inactive && lp.chargeRequired {
|
|
lp.chargeRequired = false
|
|
lp.Publish("timerActive", lp.chargeRequired)
|
|
}
|
|
|
|
return !inactive
|
|
}
|
|
|
|
// Handle adjusts current up/down to achieve desired target time taking.
|
|
func (lp *Timer) Handle() float64 {
|
|
switch {
|
|
case lp.finishAt.Before(lp.Time.Add(-deviation)):
|
|
lp.current--
|
|
lp.log.DEBUG.Printf("target charging: slowdown")
|
|
|
|
case lp.finishAt.After(lp.Time):
|
|
lp.current++
|
|
lp.log.DEBUG.Printf("target charging: speedup")
|
|
}
|
|
|
|
lp.current = math.Max(math.Min(lp.current, float64(lp.maxCurrent)), 0)
|
|
|
|
return lp.current
|
|
}
|