Refactor loadpoint configuration (#46)

This commit is contained in:
andig 2020-04-23 07:56:06 +02:00
parent bcebf03cdb
commit 62ffaa59a1
12 changed files with 263 additions and 250 deletions

View file

@ -5,7 +5,7 @@ import (
)
// DecodeOther uses mapstructure to decode into target structure. Unused keys cause errors.
func DecodeOther(log *Logger, other map[string]interface{}, cc interface{}) {
func DecodeOther(log *Logger, other interface{}, cc interface{}) {
decoderConfig := &mapstructure.DecoderConfig{
Result: cc,
ErrorUnused: true,

View file

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/andig/evcc/api"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/server"
"github.com/spf13/cobra"
@ -17,14 +18,11 @@ var chargerCmd = &cobra.Command{
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.AddCommand(chargerCmd)
configureCommand(chargerCmd)
}
func runCharger(cmd *cobra.Command, args []string) {
level, _ := cmd.PersistentFlags().GetString("log")
configureLogging(level)
configureLogging()
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
@ -35,17 +33,17 @@ func runCharger(cmd *cobra.Command, args []string) {
provider.MQTT = provider.NewMqttClient(conf.Mqtt.Broker, conf.Mqtt.User, conf.Mqtt.Password, clientID(), 1)
}
chargers := configureChargers(conf)
cp := &ConfigProvider{}
cp.configureChargers(conf)
chargers := cp.chargers
if len(args) == 1 {
arg := args[0]
chargers = map[string]api.Charger{arg: cp.Charger(arg)}
}
for name, v := range chargers {
if len(args) == 1 {
if target := args[0]; name != target {
if _, ok := chargers[target]; !ok {
log.FATAL.Fatalf("charger not found: %s", target)
}
continue
}
} else if len(chargers) != 1 {
if len(chargers) != 1 {
fmt.Println(name)
}

View file

@ -4,8 +4,11 @@ import (
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/charger"
"github.com/andig/evcc/core"
"github.com/andig/evcc/push"
"github.com/andig/evcc/server"
"github.com/andig/evcc/vehicle"
)
type config struct {
@ -17,9 +20,14 @@ type config struct {
Menu []server.MenuConfig
Messaging messagingConfig
Meters []namedConfig
Chargers []typedConfig
Vehicles []typedConfig
LoadPoints []loadPointConfig
Chargers []qualifiedConfig
Vehicles []qualifiedConfig
LoadPoints []core.Config
}
type qualifiedConfig struct {
Name, Type string
Other map[string]interface{} `mapstructure:",remain"`
}
type namedConfig struct {
@ -28,18 +36,13 @@ type namedConfig struct {
}
type typedConfig struct {
Name, Type string
Other map[string]interface{} `mapstructure:",remain"`
Type string
Other map[string]interface{} `mapstructure:",remain"`
}
type messagingConfig struct {
Events map[string]push.EventTemplate
Services []messagingService
}
type messagingService struct {
Type string
Other map[string]interface{} `mapstructure:",remain"`
Services []typedConfig
}
type mqttConfig struct {
@ -56,19 +59,63 @@ type influxConfig struct {
Interval time.Duration
}
type loadPointConfig struct {
Name string
GridMeter string // api.Meter
PVMeter string // api.Meter
ChargeMeter string // api.Meter
Charger string // api.Charger
Vehicle string // api.Vehicle
Mode api.ChargeMode
Phases int64
MinCurrent int64
MaxCurrent int64
Steepness int64
GuardDuration time.Duration
Voltage float64
ResidualPower float64
// ConfigProvider provides configuration items
type ConfigProvider struct {
meters map[string]api.Meter
chargers map[string]api.Charger
vehicles map[string]api.Vehicle
}
// Meter provides meters by name
func (c *ConfigProvider) Meter(name string) api.Meter {
if meter, ok := c.meters[name]; ok {
return meter
}
log.FATAL.Fatalf("config: invalid meter %s", name)
return nil
}
// Charger provides chargers by name
func (c *ConfigProvider) Charger(name string) api.Charger {
if charger, ok := c.chargers[name]; ok {
return charger
}
log.FATAL.Fatalf("config: invalid charger %s", name)
return nil
}
// Vehicle provides vehicles by name
func (c *ConfigProvider) Vehicle(name string) api.Vehicle {
if vehicle, ok := c.vehicles[name]; ok {
return vehicle
}
log.FATAL.Fatalf("config: invalid vehicle %s", name)
return nil
}
func (c *ConfigProvider) configure(conf config) {
c.configureMeters(conf)
c.configureChargers(conf)
c.configureVehicles(conf)
}
func (c *ConfigProvider) configureMeters(conf config) {
c.meters = make(map[string]api.Meter)
for _, cc := range conf.Meters {
c.meters[cc.Name] = core.NewMeterFromConfig(log, cc.Other)
}
}
func (c *ConfigProvider) configureChargers(conf config) {
c.chargers = make(map[string]api.Charger)
for _, cc := range conf.Chargers {
c.chargers[cc.Name] = charger.NewFromConfig(log, cc.Type, cc.Other)
}
}
func (c *ConfigProvider) configureVehicles(conf config) {
c.vehicles = make(map[string]api.Vehicle)
for _, cc := range conf.Vehicles {
c.vehicles[cc.Name] = vehicle.NewFromConfig(log, cc.Type, cc.Other)
}
}

View file

@ -5,6 +5,7 @@ import (
"os"
"testing"
"github.com/andig/evcc/provider"
"github.com/spf13/viper"
)
@ -35,6 +36,11 @@ func TestDistConfig(t *testing.T) {
log.FATAL.Fatalf("config: failed parsing config file %s: %v", cfgFile, err)
}
// setup mqtt
if viper.Get("mqtt") != nil {
provider.MQTT = provider.NewMqttClient(conf.Mqtt.Broker, conf.Mqtt.User, conf.Mqtt.Password, clientID(), 1)
}
// check config is valid
loadConfig(conf, nil)
}

View file

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/andig/evcc/api"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/server"
"github.com/spf13/cobra"
@ -17,14 +18,11 @@ var meterCmd = &cobra.Command{
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.AddCommand(meterCmd)
configureCommand(meterCmd)
}
func runMeter(cmd *cobra.Command, args []string) {
level, _ := cmd.PersistentFlags().GetString("log")
configureLogging(level)
configureLogging()
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
@ -35,17 +33,17 @@ func runMeter(cmd *cobra.Command, args []string) {
provider.MQTT = provider.NewMqttClient(conf.Mqtt.Broker, conf.Mqtt.User, conf.Mqtt.Password, clientID(), 1)
}
meters := configureMeters(conf)
cp := &ConfigProvider{}
cp.configureMeters(conf)
meters := cp.meters
if len(args) == 1 {
arg := args[0]
meters = map[string]api.Meter{arg: cp.Meter(arg)}
}
for name, v := range meters {
if len(args) == 1 {
if target := args[0]; name != target {
if _, ok := meters[target]; !ok {
log.FATAL.Fatalf("meter not found: %s", target)
}
continue
}
} else if len(meters) != 1 {
if len(meters) != 1 {
fmt.Println(name)
}

View file

@ -114,9 +114,12 @@ func Execute() {
}
}
func configureLogging(level string) {
func configureLogging() {
level := viper.GetString("log")
api.OutThreshold = api.LogLevelToThreshold(level)
api.LogThreshold = api.OutThreshold
api.Loggers(func(name string, logger *api.Logger) {
logger.SetStdoutThreshold(api.OutThreshold)
})
@ -158,12 +161,12 @@ func tee(in chan core.Param) (chan core.Param, <-chan core.Param) {
}
func run(cmd *cobra.Command, args []string) {
configureLogging(viper.GetString("log"))
configureLogging()
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config and re-configure logging after reading config file
conf := loadConfigFile(cfgFile)
configureLogging(viper.GetString("log"))
configureLogging()
go checkVersion()

View file

@ -6,10 +6,8 @@ import (
"time"
"github.com/andig/evcc/api"
"github.com/andig/evcc/charger"
"github.com/andig/evcc/core"
"github.com/andig/evcc/push"
"github.com/andig/evcc/vehicle"
"github.com/spf13/viper"
)
@ -36,108 +34,22 @@ func clientID() string {
return fmt.Sprintf("evcc-%d", pid)
}
func configureMeters(conf config) (meters map[string]api.Meter) {
meters = make(map[string]api.Meter)
for _, cc := range conf.Meters {
meters[cc.Name] = core.NewMeterFromConfig(log, cc.Other)
}
return
}
func configureChargers(conf config) (chargers map[string]api.Charger) {
chargers = make(map[string]api.Charger)
for _, cc := range conf.Chargers {
chargers[cc.Name] = charger.NewFromConfig(log, cc.Type, cc.Other)
}
return
}
func configureVehicles(conf config) (vehicles map[string]api.Vehicle) {
vehicles = make(map[string]api.Vehicle)
for _, cc := range conf.Vehicles {
vehicles[cc.Name] = vehicle.NewFromConfig(log, cc.Type, cc.Other)
}
return
}
// TODO rewrite based on https://github.com/spf13/viper/pull/863
func configureLoadPoint(lp *core.LoadPoint, lpc loadPointConfig, subv *viper.Viper) {
// for _, key := range []string{"charger", "gridmeter", "pvmeter", "chargemeter", "soc"} {
// delete(kv, key)
// }
// config := &mapstructure.DecoderConfig{
// WeaklyTypedInput: true,
// DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
// Result: lp,
// }
// decoder, err := mapstructure.NewDecoder(config)
// if err != nil {
// log.FATAL.Fatalf("configuring loadpoints failed: %v", err)
// }
// if err := decoder.Decode(kv); err != nil {
// log.FATAL.Fatalf("configuring loadpoints failed: %v", err)
// }
// we can ignore the error here as UnmarshalExact has been called before
_ = subv.UnmarshalExact(lp)
if lpc.Mode != "" {
// workaround for golangs yaml off=0 conversion
if lpc.Mode == "0" {
lpc.Mode = api.ModeOff
}
lp.Mode = lpc.Mode // don't use SetMode here as that will block on channel send
}
}
func loadConfig(conf config, eventsChan chan push.Event) (loadPoints []*core.LoadPoint) {
meters := configureMeters(conf)
chargers := configureChargers(conf)
vehicles := configureVehicles(conf)
cp := &ConfigProvider{}
cp.configure(conf)
for idx, lpc := range conf.LoadPoints {
// configure loadpoint
lp := core.NewLoadPoint()
subv := viper.SubSlice("loadpoints")[idx]
configureLoadPoint(lp, lpc, subv)
// slice of loadpoints
lps, ok := viper.AllSettings()["loadpoints"]
if !ok {
log.FATAL.Fatal("config: missing loadpoints")
}
// assign charger
if charger, ok := chargers[lpc.Charger]; ok {
lp.Charger = charger
} else {
log.FATAL.Fatalf("invalid charger '%s'", lpc.Charger)
}
// assign meters
for _, m := range []struct {
key string
meter *api.Meter
}{
{lpc.GridMeter, &lp.GridMeter},
{lpc.ChargeMeter, &lp.ChargeMeter},
{lpc.PVMeter, &lp.PVMeter},
} {
if m.key != "" {
if impl, ok := meters[m.key]; ok {
*m.meter = impl
} else {
log.FATAL.Fatalf("invalid meter '%s'", m.key)
}
}
}
// assign socs
if lpc.Vehicle != "" {
if impl, ok := vehicles[lpc.Vehicle]; ok {
lp.Vehicle = impl
} else {
log.FATAL.Fatalf("invalid vehicle '%s'", lpc.Vehicle)
}
}
// decode slice into slice of maps
var lpm []map[string]interface{}
api.DecodeOther(log, lps, &lpm)
for _, lpc := range lpm {
lp := core.NewLoadPointFromConfig(log, cp, lpc)
loadPoints = append(loadPoints, lp)
}

View file

@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"github.com/andig/evcc/api"
"github.com/andig/evcc/provider"
"github.com/andig/evcc/server"
"github.com/spf13/cobra"
@ -17,14 +18,11 @@ var vehicleCmd = &cobra.Command{
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.AddCommand(vehicleCmd)
configureCommand(vehicleCmd)
}
func runVehicle(cmd *cobra.Command, args []string) {
level, _ := cmd.PersistentFlags().GetString("log")
configureLogging(level)
configureLogging()
log.INFO.Printf("evcc %s (%s)", server.Version, server.Commit)
// load config
@ -35,17 +33,17 @@ func runVehicle(cmd *cobra.Command, args []string) {
provider.MQTT = provider.NewMqttClient(conf.Mqtt.Broker, conf.Mqtt.User, conf.Mqtt.Password, clientID(), 1)
}
vehicles := configureVehicles(conf)
cp := &ConfigProvider{}
cp.configureVehicles(conf)
vehicles := cp.vehicles
if len(args) == 1 {
arg := args[0]
vehicles = map[string]api.Vehicle{arg: cp.Vehicle(arg)}
}
for name, v := range vehicles {
if len(args) == 1 {
if target := args[0]; name != target {
if _, ok := vehicles[target]; !ok {
log.FATAL.Fatalf("charger not found: %s", target)
}
continue
}
} else if len(vehicles) != 1 {
if len(vehicles) != 1 {
fmt.Println(name)
}

View file

@ -42,30 +42,30 @@ func (lp *LoadPoint) Configuration() Configuration {
Phases: lp.Phases,
MinCurrent: lp.MinCurrent,
MaxCurrent: lp.MaxCurrent,
GridMeter: lp.GridMeter != nil,
PVMeter: lp.PVMeter != nil,
GridMeter: lp.gridMeter != nil,
PVMeter: lp.pvMeter != nil,
ChargeMeter: lp.hasChargeMeter(),
}
if lp.Vehicle != nil {
if lp.vehicle != nil {
c.SoC = true
c.SoCCapacity = lp.Vehicle.Capacity()
c.SoCTitle = lp.Vehicle.Title()
c.SoCCapacity = lp.vehicle.Capacity()
c.SoCTitle = lp.vehicle.Title()
}
return c
}
func (lp *LoadPoint) hasChargeMeter() bool {
_, isWrapped := lp.ChargeMeter.(*wrapper.ChargeMeter)
return lp.ChargeMeter != nil && !isWrapped
_, isWrapped := lp.chargeMeter.(*wrapper.ChargeMeter)
return lp.chargeMeter != nil && !isWrapped
}
// Dump loadpoint configuration
func (lp *LoadPoint) Dump() {
vehicle := lp.Vehicle != nil
grid := lp.GridMeter != nil
pv := lp.PVMeter != nil
vehicle := lp.vehicle != nil
grid := lp.gridMeter != nil
pv := lp.pvMeter != nil
log.INFO.Printf("%s config: vehicle %s grid %s pv %s charge %s", lp.Name,
presence[vehicle],
presence[grid],
@ -73,9 +73,9 @@ func (lp *LoadPoint) Dump() {
presence[lp.hasChargeMeter()],
)
_, power := lp.Charger.(api.Meter)
_, energy := lp.Charger.(api.ChargeRater)
_, timer := lp.Charger.(api.ChargeTimer)
_, power := lp.charger.(api.Meter)
_, energy := lp.charger.(api.ChargeRater)
_, timer := lp.charger.(api.ChargeTimer)
log.INFO.Printf("%s charger: power %s energy %s timer %s", lp.Name,
presence[power],
presence[energy],
@ -115,7 +115,7 @@ func (lp *LoadPoint) SetMode(mode api.ChargeMode) {
// chargeDuration returns for how long the charge cycle has been running
func (lp *LoadPoint) chargeDuration() time.Duration {
d, err := lp.ChargeTimer.ChargingTime()
d, err := lp.chargeTimer.ChargingTime()
if err != nil {
log.ERROR.Printf("%s charge timer error: %v", lp.Name, err)
}
@ -124,7 +124,7 @@ func (lp *LoadPoint) chargeDuration() time.Duration {
// chargedEnergy returns energy consumption since charge start in kWh
func (lp *LoadPoint) chargedEnergy() float64 {
f, err := lp.ChargeRater.ChargedEnergy()
f, err := lp.chargeRater.ChargedEnergy()
if err != nil {
log.ERROR.Printf("%s charge rater error: %v", lp.Name, err)
}
@ -137,8 +137,8 @@ func (lp *LoadPoint) remainingChargeDuration(chargePercent float64) time.Duratio
return -1
}
if lp.chargePower > 0 && lp.Vehicle != nil {
whRemaining := (1 - chargePercent/100.0) * 1e3 * float64(lp.Vehicle.Capacity())
if lp.chargePower > 0 && lp.vehicle != nil {
whRemaining := (1 - chargePercent/100.0) * 1e3 * float64(lp.vehicle.Capacity())
return time.Duration(float64(time.Hour) * whRemaining / lp.chargePower)
}
@ -147,12 +147,12 @@ func (lp *LoadPoint) remainingChargeDuration(chargePercent float64) time.Duratio
// publish state of charge and remaining charge duration
func (lp *LoadPoint) publishSoC() {
if lp.Vehicle == nil {
if lp.vehicle == nil {
return
}
if lp.connected() {
f, err := lp.Vehicle.ChargeState()
f, err := lp.vehicle.ChargeState()
if err == nil {
log.DEBUG.Printf("%s vehicle charge: %.1f%%", lp.Name, f)
lp.publish("socCharge", f)

View file

@ -33,6 +33,28 @@ func powerToCurrent(power, voltage float64, phases int64) int64 {
return int64(power / (float64(phases) * voltage))
}
// Config contains the public loadpoint configuration
type Config struct {
Name string
Mode api.ChargeMode // Charge mode, guarded by mutex
// options
Sensitivity int64 // Step size of current change
Phases int64 // Phases- required for converting power and current.
MinCurrent int64 // PV mode: start current Min+PV mode: min current
MaxCurrent int64 // Max allowed current. Physically ensured by the charge controller
Voltage float64 // Operating voltage. 230V for Germany.
ResidualPower float64 // PV meter only: household usage. Grid meter: household safety margin
ChargerRef string `mapstructure:"charger"` // Charger reference
GridMeterRef string `mapstructure:"gridmeter"` // Grid usage meter reference
PVMeterRef string `mapstructure:"pvmeter"` // PV generation meter reference
ChargeMeterRef string `mapstructure:"chargemeter"` // Charger usage meter reference
VehicleRef string `mapstructure:"vehicle"` // Vehicle reference
GuardDuration time.Duration // charger enable/disable minimum holding time
}
// LoadPoint is responsible for controlling charge depending on
// SoC needs and power availability.
type LoadPoint struct {
@ -43,27 +65,19 @@ type LoadPoint struct {
notificationChan chan<- push.Event // notifications
uiChan chan<- Param // client push messages
Name string
Charger api.Charger
ChargeTimer api.ChargeTimer
ChargeRater api.ChargeRater
Config `mapstructure:",squash"` // exposed public configuration
chargeTimer api.ChargeTimer
chargeRater api.ChargeRater
// meters
GridMeter api.Meter // Grid usage meter
PVMeter api.Meter // PV generation meter
ChargeMeter api.Meter // Charger usage meter
Vehicle api.Vehicle // Vehicle
// options
Steepness int64 // Step size of current change
Phases int64 // Phases- required for converting power and current.
MinCurrent int64 // PV mode: start current Min+PV mode: min current
MaxCurrent int64 // Max allowed current. Physically ensured by the charge controller
Voltage float64 // Operating voltage. 230V for Germany.
ResidualPower float64 // PV meter only: household usage. Grid meter: household safety margin
charger api.Charger // Charger
gridMeter api.Meter // Grid usage meter
pvMeter api.Meter // PV generation meter
chargeMeter api.Meter // Charger usage meter
vehicle api.Vehicle // Vehicle
// cached state
Mode api.ChargeMode // Charge mode, guarded by mutex
status api.ChargeStatus // Charger status
targetCurrent int64 // Allowed current. Between MinCurrent and MaxCurrent.
enabled bool // Charger enabled state
@ -73,26 +87,63 @@ type LoadPoint struct {
chargePower float64 // Charging power
// contactor switch guard
guardUpdated time.Time // charger enabled/disabled timestamp
GuardDuration time.Duration // charger enable/disable minimum holding time
guardUpdated time.Time // charger enabled/disabled timestamp
}
// configProvider gives access to configuration repository
type configProvider interface {
Meter(string) api.Meter
Charger(string) api.Charger
Vehicle(string) api.Vehicle
}
// NewLoadPointFromConfig creates a new loadpoint
func NewLoadPointFromConfig(log *api.Logger, cp configProvider, other map[string]interface{}) *LoadPoint {
lp := NewLoadPoint()
api.DecodeOther(log, other, &lp)
if lp.ChargerRef != "" {
lp.charger = cp.Charger(lp.ChargerRef)
} else {
log.FATAL.Fatal("config: missing charger")
}
if lp.PVMeterRef == "" && lp.GridMeterRef == "" {
log.FATAL.Fatal("config: missing either pv or grid meter")
}
if lp.GridMeterRef != "" {
lp.gridMeter = cp.Meter(lp.GridMeterRef)
}
if lp.ChargeMeterRef != "" {
lp.chargeMeter = cp.Meter(lp.ChargeMeterRef)
}
if lp.PVMeterRef != "" {
lp.pvMeter = cp.Meter(lp.PVMeterRef)
}
if lp.VehicleRef != "" {
lp.vehicle = cp.Vehicle(lp.VehicleRef)
}
return lp
}
// NewLoadPoint creates a LoadPoint with sane defaults
func NewLoadPoint() *LoadPoint {
return &LoadPoint{
clock: clock.New(),
bus: evbus.New(),
triggerChan: make(chan struct{}, 1),
Name: "Main",
Mode: api.ModeOff,
clock: clock.New(),
bus: evbus.New(),
triggerChan: make(chan struct{}, 1),
Config: Config{
Name: "main",
Mode: api.ModeOff,
Phases: 1,
Voltage: 230, // V
MinCurrent: 6, // A
MaxCurrent: 16, // A
Sensitivity: 10, // A
GuardDuration: 10 * time.Minute,
},
status: api.StatusNone,
Phases: 1,
Voltage: 230, // V
MinCurrent: 6, // A
MaxCurrent: 16, // A
Steepness: 10, // A
targetCurrent: 0, // A
GuardDuration: 10 * time.Minute,
targetCurrent: 0, // A
}
}
@ -123,12 +174,12 @@ func (lp *LoadPoint) evChargeStartHandler() {
// evChargeStartHandler sends external stop event
func (lp *LoadPoint) evChargeStopHandler() {
energy, err := lp.ChargeRater.ChargedEnergy()
energy, err := lp.chargeRater.ChargedEnergy()
if err != nil {
log.ERROR.Printf("%s charged energy: %v", lp.Name, err)
}
duration, err := lp.ChargeTimer.ChargingTime()
duration, err := lp.chargeTimer.ChargingTime()
if err != nil {
log.ERROR.Printf("%s charge duration: %v", lp.Name, err)
}
@ -162,14 +213,14 @@ func (lp *LoadPoint) Prepare(uiChan chan<- Param, notificationChan chan<- push.E
lp.notificationChan = notificationChan
lp.uiChan = uiChan
if lp.PVMeter == nil && lp.GridMeter == nil {
log.FATAL.Fatal("missing either PV or Grid meter - aborting")
if lp.pvMeter == nil && lp.gridMeter == nil {
log.FATAL.Fatal("missing either pv or grid meter")
}
// ensure charge meter exists
if lp.ChargeMeter == nil {
if mt, ok := lp.Charger.(api.Meter); ok {
lp.ChargeMeter = mt
if lp.chargeMeter == nil {
if mt, ok := lp.charger.(api.Meter); ok {
lp.chargeMeter = mt
} else {
mt := &wrapper.ChargeMeter{
Phases: lp.Phases,
@ -179,29 +230,29 @@ func (lp *LoadPoint) Prepare(uiChan chan<- Param, notificationChan chan<- push.E
_ = lp.bus.Subscribe(evStopCharge, func() {
mt.SetChargeCurrent(0)
})
lp.ChargeMeter = mt
lp.chargeMeter = mt
}
}
// ensure charge rater exists
if rt, ok := lp.Charger.(api.ChargeRater); ok {
lp.ChargeRater = rt
if rt, ok := lp.charger.(api.ChargeRater); ok {
lp.chargeRater = rt
} else {
rt := wrapper.NewChargeRater(lp.Name, lp.ChargeMeter)
rt := wrapper.NewChargeRater(lp.Name, lp.chargeMeter)
_ = lp.bus.Subscribe(evChargePower, rt.SetChargePower)
_ = lp.bus.Subscribe(evStartCharge, rt.StartCharge)
_ = lp.bus.Subscribe(evStopCharge, rt.StopCharge)
lp.ChargeRater = rt
lp.chargeRater = rt
}
// ensure charge timer exists
if ct, ok := lp.Charger.(api.ChargeTimer); ok {
lp.ChargeTimer = ct
if ct, ok := lp.charger.(api.ChargeTimer); ok {
lp.chargeTimer = ct
} else {
ct := wrapper.NewChargeTimer()
_ = lp.bus.Subscribe(evStartCharge, ct.StartCharge)
_ = lp.bus.Subscribe(evStopCharge, ct.StopCharge)
lp.ChargeTimer = ct
lp.chargeTimer = ct
}
// event handlers
@ -209,7 +260,7 @@ func (lp *LoadPoint) Prepare(uiChan chan<- Param, notificationChan chan<- push.E
_ = lp.bus.Subscribe(evStopCharge, lp.evChargeStopHandler)
// read initial enabled state
enabled, err := lp.Charger.Enabled()
enabled, err := lp.charger.Enabled()
if err == nil {
lp.enabled = enabled
log.INFO.Printf("%s charger %sd", lp.Name, status[lp.enabled])
@ -245,7 +296,7 @@ func (lp *LoadPoint) chargerEnable(enable bool) error {
return nil
}
err := lp.Charger.Enable(enable)
err := lp.charger.Enable(enable)
if err == nil {
lp.enabled = enable // cache
log.INFO.Printf("%s charger %s", lp.Name, status[enable])
@ -281,7 +332,7 @@ func (lp *LoadPoint) chargingCycle(enable bool) {
// updateChargeStatus updates car status and stops charging if car disconnected
func (lp *LoadPoint) updateChargeStatus() api.ChargeStatus {
// abort if no vehicle connected
status, err := lp.Charger.Status()
status, err := lp.charger.Status()
if err != nil {
log.ERROR.Printf("%s charger error: %v", lp.Name, err)
return api.StatusNone
@ -324,7 +375,7 @@ func (lp *LoadPoint) setTargetCurrent(targetCurrentIn int64) error {
if lp.targetCurrent != targetCurrent {
log.DEBUG.Printf("%s set charge current: %dA", lp.Name, targetCurrent)
if err := lp.Charger.MaxCurrent(targetCurrent); err != nil {
if err := lp.charger.MaxCurrent(targetCurrent); err != nil {
return fmt.Errorf("%s charge controller error: %v", lp.Name, err)
}
@ -345,9 +396,9 @@ func (lp *LoadPoint) rampUpDown(target int64) error {
var step int64
if current < target {
step = min(current+lp.Steepness, target)
step = min(current+lp.Sensitivity, target)
} else if current > target {
step = max(current-lp.Steepness, target)
step = max(current-lp.Sensitivity, target)
}
step = clamp(step, lp.MinCurrent, lp.MaxCurrent)
@ -442,9 +493,9 @@ func (lp *LoadPoint) updateMeters() (err error) {
}
// read PV meter before charge meter
retryMeter("grid", lp.GridMeter, &lp.gridPower)
retryMeter("pv", lp.PVMeter, &lp.pvPower)
retryMeter("charge", lp.ChargeMeter, &lp.chargePower)
retryMeter("grid", lp.gridMeter, &lp.gridPower)
retryMeter("pv", lp.pvMeter, &lp.pvPower)
retryMeter("charge", lp.chargeMeter, &lp.chargePower)
return err
}

View file

@ -33,8 +33,8 @@ func TestNew(t *testing.T) {
if lp.MaxCurrent != lpMaxCurrent {
t.Errorf("MaxCurrent %v", lp.MaxCurrent)
}
if lp.Steepness != 10 {
t.Errorf("Steepness %v", lp.Steepness)
if lp.Sensitivity != 10 {
t.Errorf("Sensitivity %v", lp.Sensitivity)
}
if lp.status != api.StatusNone {
t.Errorf("status %v", lp.status)
@ -55,14 +55,14 @@ func newLoadPoint(charger api.Charger, pv, gm, cm api.Meter) *LoadPoint {
lp.clock = clock.NewMock()
lp.clock.(*clock.Mock).Add(time.Hour)
lp.Charger = charger
lp.PVMeter = pv
lp.GridMeter = gm
lp.charger = charger
lp.pvMeter = pv
lp.gridMeter = gm
// prevent assigning a nil pointer sake of
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I/discussion
if !(cm == nil || reflect.ValueOf(cm).IsNil()) {
lp.ChargeMeter = cm
lp.chargeMeter = cm
}
uiChan := make(chan Param)

View file

@ -154,7 +154,7 @@ loadpoints:
gridmeter: grid # grid meter
pvmeter: pv # pv meter
chargemeter: charge # charge meter
steepness: 1 # raise/lower charge current in 1A steps
sensitivity: 1 # current raise/lower steps size (default 1A)
guardduration: 10m # switch charger contactor not more often than this (default 10m)
maxcurrent: 16 # maximum charge current (default 16A)
phases: 3 # ev phases (default 3)