From 62ffaa59a17f1d5f617cd40002743778ede62487 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 23 Apr 2020 07:56:06 +0200 Subject: [PATCH] Refactor loadpoint configuration (#46) --- api/decoder.go | 2 +- cmd/charger.go | 24 +++--- cmd/config.go | 99 ++++++++++++++++++------- cmd/config_test.go | 6 ++ cmd/meter.go | 24 +++--- cmd/root.go | 9 ++- cmd/setup.go | 112 +++------------------------- cmd/vehicle.go | 24 +++--- core/api.go | 38 +++++----- core/loadpoint.go | 161 +++++++++++++++++++++++++++-------------- core/loadpoint_test.go | 12 +-- evcc.dist.yaml | 2 +- 12 files changed, 263 insertions(+), 250 deletions(-) diff --git a/api/decoder.go b/api/decoder.go index b5309738a..395cdb989 100644 --- a/api/decoder.go +++ b/api/decoder.go @@ -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, diff --git a/cmd/charger.go b/cmd/charger.go index 6c36ff12a..90fb61e46 100644 --- a/cmd/charger.go +++ b/cmd/charger.go @@ -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) } diff --git a/cmd/config.go b/cmd/config.go index df3e64fbc..f78aba344 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -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) + } } diff --git a/cmd/config_test.go b/cmd/config_test.go index b89be3783..67a20cc4c 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -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) } diff --git a/cmd/meter.go b/cmd/meter.go index 6f930fada..17bf48459 100644 --- a/cmd/meter.go +++ b/cmd/meter.go @@ -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) } diff --git a/cmd/root.go b/cmd/root.go index 3ef222ed3..260f77e7b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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() diff --git a/cmd/setup.go b/cmd/setup.go index 2c78c629f..e690c88c5 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -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) } diff --git a/cmd/vehicle.go b/cmd/vehicle.go index a7b6526c3..113226449 100644 --- a/cmd/vehicle.go +++ b/cmd/vehicle.go @@ -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) } diff --git a/core/api.go b/core/api.go index 9054a66bb..bd24dcc0d 100644 --- a/core/api.go +++ b/core/api.go @@ -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) diff --git a/core/loadpoint.go b/core/loadpoint.go index e3ce15574..a53c48056 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -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 } diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index 1ba04982c..56d65e121 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -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) diff --git a/evcc.dist.yaml b/evcc.dist.yaml index b146f632a..6faa3bb91 100644 --- a/evcc.dist.yaml +++ b/evcc.dist.yaml @@ -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)