evcc-io/core/loadpoint_api.go
2020-03-06 08:24:05 +01:00

217 lines
5.3 KiB
Go

package core
import (
"context"
"errors"
"math"
"sync"
"time"
"github.com/andig/evcc/api"
)
// Param is the broadcast channel data type
type Param struct {
Key string
Val interface{}
}
// Configuration is the loadpoint feature structure
type Configuration struct {
Mode string `json:"mode"`
Phases int64 `json:"phases"`
MinCurrent int64 `json:"minCurrent"`
MaxCurrent int64 `json:"maxCurrent"`
GridMeter bool `json:"gridMeter"`
PVMeter bool `json:"pvMeter"`
ChargeMeter bool `json:"chargeMeter"`
SoC bool `json:"soc"`
SoCCapacity int64 `json:"socCapacity"`
SoCTitle string `json:"socTitle"`
}
// Configuration returns meter configuration
func (lp *LoadPoint) Configuration() Configuration {
c := Configuration{
Mode: string(lp.state.Mode()),
Phases: lp.Phases,
MinCurrent: lp.MinCurrent,
MaxCurrent: lp.MaxCurrent,
GridMeter: lp.GridMeter != nil,
PVMeter: lp.PVMeter != nil,
ChargeMeter: lp.chargeMeterPresent(),
}
if lp.SoC != nil {
c.SoC = true
if soc, ok := lp.SoC.(*SoC); ok {
c.SoCCapacity = soc.Capacity
c.SoCTitle = soc.Title
}
}
return c
}
// ChargeMode updates charge mode
func (lp *LoadPoint) ChargeMode(mode api.ChargeMode) error {
// pv modes require GridMeter
if mode == api.ModeMinPV || mode == api.ModePV {
// check if charger is controllable
_, chargerControllable := lp.Charger.(api.ChargeController)
if (lp.PVMeter == nil && lp.GridMeter == nil) || !chargerControllable {
return errors.New("invalid charge mode: " + string(mode))
}
}
if lp.state.Mode() != mode {
// start cycle detection async from http call
go lp.chargingCycle(mode != api.ModeOff)
// update mode and enable/disable charger
lp.chargeMode(mode)
return lp.chargerEnable(mode != api.ModeOff)
}
return nil
}
// ChargeDuration returns for how long the charge cycle has been running
func (lp *LoadPoint) ChargeDuration() time.Duration {
d, err := lp.ChargeTimer.ChargingTime()
if err != nil {
log.ERROR.Printf("%s charge timer error: %v", lp.Name, err)
}
return d
}
// ChargedEnergy returns energy consumption since charge start in Wh
func (lp *LoadPoint) ChargedEnergy() float64 {
f, err := lp.ChargeRater.ChargedEnergy()
if err != nil {
log.ERROR.Printf("%s charge rater error: %v", lp.Name, err)
}
return f
}
// Connected returns the EVs connection state
func (lp *LoadPoint) Connected() bool {
return lp.state.Status() != api.StatusA
}
// Charging returns the EVs charging state
func (lp *LoadPoint) Charging() bool {
return lp.state.Status() == api.StatusC
}
// SoCChargeState returns the soc battery charge state
func (lp *LoadPoint) SoCChargeState() float64 {
if !lp.Connected() {
return math.NaN()
}
f, err := lp.SoC.ChargeState()
if err != nil {
log.ERROR.Printf("%s soc error: %v", lp.Name, err)
return math.NaN()
}
lp.state.SetSocCharge(f)
return f
}
// ChargeRemainingDuration returns the remaining charge time
func (lp *LoadPoint) ChargeRemainingDuration() time.Duration {
if !lp.state.Charging() {
return -1
}
if currentPower := lp.state.ChargePower(); currentPower > 0 {
if soc, ok := lp.SoC.(*SoC); ok {
whRemaining := (1 - lp.state.SocCharge()/100.0) * 1000.0 * float64(soc.Capacity)
return time.Duration(float64(time.Hour) * whRemaining / currentPower)
}
}
return -1
}
func (lp *LoadPoint) publishMeter(name string, meter api.Meter, clientPush chan Param) {
if f, err := meter.CurrentPower(); err == nil {
key := name + "Power"
log.TRACE.Printf("%s %s power: %.1fW", lp.Name, name, f)
clientPush <- Param{Key: key, Val: f}
} else {
log.ERROR.Printf("%s %v", lp.Name, err)
}
}
func (lp *LoadPoint) publishCharging(clientPush chan Param) {
if lp.Charging() {
if f, err := lp.Charger.ActualCurrent(); err == nil {
log.TRACE.Printf("%s charge current: %dA", lp.Name, f)
clientPush <- Param{Key: "chargeCurrent", Val: f}
} else {
log.ERROR.Printf("%s update charger current failed: %v", lp.Name, err)
}
} else {
clientPush <- Param{Key: "chargeCurrent", Val: 0.0}
}
}
func (lp *LoadPoint) publishSoC(clientPush chan Param) {
f := lp.SoCChargeState()
if math.IsNaN(f) {
log.TRACE.Printf("%s soc charge: —", lp.Name)
clientPush <- Param{Key: "socCharge", Val: "—"}
} else {
log.TRACE.Printf("%s soc charge: %.1f%%", lp.Name, f)
clientPush <- Param{Key: "socCharge", Val: f}
}
clientPush <- Param{Key: "chargeEstimate", Val: lp.ChargeRemainingDuration()}
}
// Publish publishes loadpoint data to broadcast channel
func (lp *LoadPoint) Publish(ctx context.Context, clientPush chan Param) {
clientPush <- Param{Key: "mode", Val: string(lp.state.Mode())}
clientPush <- Param{Key: "connected", Val: lp.Connected()}
clientPush <- Param{Key: "charging", Val: lp.Charging()}
var wg sync.WaitGroup
for name, meter := range map[string]api.Meter{
"grid": lp.GridMeter,
"pv": lp.PVMeter,
"charge": lp.ChargeMeter,
} {
if meter != nil {
wg.Add(1)
go func(name string, meter api.Meter) {
lp.publishMeter(name, meter, clientPush)
wg.Done()
}(name, meter)
}
}
wg.Add(1)
go func() {
lp.publishCharging(clientPush)
wg.Done()
}()
if lp.SoC != nil {
wg.Add(1)
go func() {
lp.publishSoC(clientPush)
wg.Done()
}()
}
wg.Wait()
clientPush <- Param{Key: "chargedEnergy", Val: lp.ChargedEnergy()}
clientPush <- Param{Key: "chargeDuration", Val: lp.ChargeDuration()}
}