diff --git a/api/api.go b/api/api.go index fe7f88181..a157777fc 100644 --- a/api/api.go +++ b/api/api.go @@ -1,6 +1,10 @@ package api -import "time" +import ( + "encoding/json" + "fmt" + "time" +) //go:generate mockgen -package mock -destination ../mock/mock_api.go github.com/evcc-io/evcc/api Charger,ChargeState,ChargePhases,Identifier,Meter,MeterEnergy,Vehicle,ChargeRater,Battery @@ -42,11 +46,20 @@ func (c ChargeStatus) String() string { // ActionConfig defines an action to take on event type ActionConfig struct { - Mode ChargeMode `mapstructure:"mode"` // Charge mode - MinCurrent float64 `mapstructure:"minCurrent"` // Minimum Current - MaxCurrent float64 `mapstructure:"maxCurrent"` // Maximum Current - MinSoC int `mapstructure:"minSoC"` // Minimum SoC - TargetSoC int `mapstructure:"targetSoC"` // Target SoC + Mode *ChargeMode `mapstructure:"mode,omitempty"` // Charge Mode + MinCurrent *float64 `mapstructure:"minCurrent,omitempty"` // Minimum Current + MaxCurrent *float64 `mapstructure:"maxCurrent,omitempty"` // Maximum Current + MinSoC *int `mapstructure:"minSoC,omitempty"` // Minimum SoC + TargetSoC *int `mapstructure:"targetSoC,omitempty"` // Target SoC +} + +// String implements Stringer +func (a ActionConfig) String() string { + if data, err := json.Marshal(a); err != nil { + return fmt.Sprintf("%v\n", err) + } else { + return fmt.Sprintf("%s\n", data) + } } // Meter is able to provide current power in W diff --git a/cmd/dumper.go b/cmd/dumper.go index 72340180b..714b0b5a2 100644 --- a/cmd/dumper.go +++ b/cmd/dumper.go @@ -162,6 +162,12 @@ func (d *dumper) Dump(name string, v interface{}) { } } + if v, ok := v.(api.Vehicle); ok { + fmt.Fprintf(w, "Capacity:\t%dkWh\n", v.Capacity()) + fmt.Fprintf(w, "Identifiers:\t%v\n", v.Identifiers()) + fmt.Fprintf(w, "OnIdentified:\t%v\n", v.OnIdentified()) + } + // Identity if v, ok := v.(api.Identifier); ok { @@ -177,10 +183,5 @@ func (d *dumper) Dump(name string, v interface{}) { v.Diagnose() } - if v, ok := v.(api.Vehicle); ok { - fmt.Fprintf(w, "Capacity:\t%dkWh\n", v.Capacity()) - fmt.Fprintf(w, "Identifiers:\t%v\n", v.Identifiers()) - } - w.Flush() } diff --git a/core/loadpoint.go b/core/loadpoint.go index bea2d5474..e948ff2b3 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -21,6 +21,7 @@ import ( evbus "github.com/asaskevich/EventBus" "github.com/avast/retry-go/v3" "github.com/benbjohnson/clock" + "github.com/cjrd/allocate" ) const ( @@ -171,13 +172,6 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin } } - if lp.SoC.Target == 0 { - lp.SoC.Target = lp.onDisconnect.TargetSoC // use disconnect value as default soc - if lp.SoC.Target == 0 { - lp.SoC.Target = 100 - } - } - if lp.MinCurrent == 0 { lp.log.WARN.Println("minCurrent must not be zero") } @@ -242,8 +236,9 @@ func NewLoadPoint(log *util.Logger) *LoadPoint { Mode: api.ModeOff, Phases: 3, status: api.StatusNone, - MinCurrent: 6, // A - MaxCurrent: 16, // A + MinCurrent: 6, // A + MaxCurrent: 16, // A + SoC: SoCConfig{Min: 0, Target: 100}, // % GuardDuration: 5 * time.Minute, } @@ -252,12 +247,19 @@ func NewLoadPoint(log *util.Logger) *LoadPoint { // collectDefaults collects default values for use on disconnect func (lp *LoadPoint) collectDefaults() { - lp.onDisconnect = api.ActionConfig{ - Mode: lp.GetMode(), - MinCurrent: lp.GetMinCurrent(), - MaxCurrent: lp.GetMaxCurrent(), - MinSoC: lp.GetMinSoC(), - TargetSoC: lp.GetTargetSoC(), + // get reference to action config + actionCfg := &lp.onDisconnect + + // allocate action config such that all pointer fields are fully allocated + if err := allocate.Zero(actionCfg); err == nil { + // initialize with default values + *actionCfg.Mode = lp.GetMode() + *actionCfg.MinCurrent = lp.GetMinCurrent() + *actionCfg.MaxCurrent = lp.GetMaxCurrent() + *actionCfg.MinSoC = lp.GetMinSoC() + *actionCfg.TargetSoC = lp.GetTargetSoC() + } else { + lp.log.ERROR.Printf("error allocating action config: %v", err) } } @@ -420,29 +422,21 @@ func (lp *LoadPoint) evChargeCurrentWrappedMeterHandler(current float64) { } // applyAction executes the action -func (lp *LoadPoint) applyAction(action api.ActionConfig) { - if action.Mode != api.ModeEmpty { - lp.SetMode(action.Mode) +func (lp *LoadPoint) applyAction(actionCfg api.ActionConfig) { + if actionCfg.Mode != nil { + lp.SetMode(*actionCfg.Mode) } - if action.MinCurrent > 0 { - lp.SetMinCurrent(action.MinCurrent) + if actionCfg.MinCurrent != nil { + lp.SetMinCurrent(*actionCfg.MinCurrent) } - if action.MaxCurrent > 0 { - lp.SetMaxCurrent(action.MaxCurrent) + if actionCfg.MaxCurrent != nil { + lp.SetMaxCurrent(*actionCfg.MaxCurrent) } - if action.MinSoC != 0 { - // TODO deduplicate with SetMinSoC - lp.Lock() - lp.SoC.Min = action.MinSoC - lp.publish("minSoC", action.MinSoC) - lp.Unlock() + if actionCfg.MinSoC != nil { + lp.SetMinSoC(*actionCfg.MinSoC) } - if action.TargetSoC != 0 { - // TODO deduplicate with SetTargetSoC - lp.Lock() - lp.SoC.Target = action.TargetSoC - lp.publish("targetSoC", action.TargetSoC) - lp.Unlock() + if actionCfg.TargetSoC != nil { + lp.SetTargetSoC(*actionCfg.TargetSoC) } } diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go index 1e0d31e3b..6a9dcefc4 100644 --- a/core/loadpoint/api.go +++ b/core/loadpoint/api.go @@ -36,11 +36,11 @@ type API interface { // GetTargetSoC returns the charge target soc GetTargetSoC() int // SetTargetSoC sets the charge target soc - SetTargetSoC(int) error + SetTargetSoC(int) // GetMinSoC returns the charge minimum soc GetMinSoC() int // SetMinSoC sets the charge minimum soc - SetMinSoC(int) error + SetMinSoC(int) // GetPhases returns the enabled phases GetPhases() int // SetPhases sets the enabled phases diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go index de46bc2d1..f9fcc2b83 100644 --- a/core/loadpoint_api.go +++ b/core/loadpoint_api.go @@ -56,14 +56,10 @@ func (lp *LoadPoint) GetTargetSoC() int { } // SetTargetSoC sets loadpoint charge target soc -func (lp *LoadPoint) SetTargetSoC(soc int) error { +func (lp *LoadPoint) SetTargetSoC(soc int) { lp.Lock() defer lp.Unlock() - if lp.vehicle == nil { - return api.ErrNotAvailable - } - lp.log.INFO.Println("set target soc:", soc) // apply immediately @@ -72,8 +68,6 @@ func (lp *LoadPoint) SetTargetSoC(soc int) error { lp.publish("targetSoC", soc) lp.requestUpdate() } - - return nil } // GetMinSoC returns loadpoint charge minimum soc @@ -84,14 +78,10 @@ func (lp *LoadPoint) GetMinSoC() int { } // SetMinSoC sets loadpoint charge minimum soc -func (lp *LoadPoint) SetMinSoC(soc int) error { +func (lp *LoadPoint) SetMinSoC(soc int) { lp.Lock() defer lp.Unlock() - if lp.vehicle == nil { - return api.ErrNotAvailable - } - lp.log.INFO.Println("set min soc:", soc) // apply immediately @@ -100,8 +90,6 @@ func (lp *LoadPoint) SetMinSoC(soc int) error { lp.publish("minSoC", soc) lp.requestUpdate() } - - return nil } // GetPhases returns loadpoint enabled phases diff --git a/go.mod b/go.mod index 8972154ec..176684acb 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/benbjohnson/clock v1.3.0 github.com/bogosj/tesla v1.0.2 github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cjrd/allocate v0.0.0-20191115010018-022b87fe59fc github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/containrrr/shoutrrr v0.5.2 github.com/deepmap/oapi-codegen v1.9.0 // indirect diff --git a/go.sum b/go.sum index 2cc7fcbbc..306c426a9 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cjrd/allocate v0.0.0-20191115010018-022b87fe59fc h1:JtJ84VruFjlxE4igdwtW8+hs0Ep34lmdThEWVXkkOYQ= +github.com/cjrd/allocate v0.0.0-20191115010018-022b87fe59fc/go.mod h1:xCdduY82QBtGJbFbch7ShY3ltvP4/a+1kBh3HzGTaEk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= diff --git a/server/http_handler.go b/server/http_handler.go index 6e3285237..4ff92ad2f 100644 --- a/server/http_handler.go +++ b/server/http_handler.go @@ -114,10 +114,8 @@ func targetSoCHandler(lp loadpoint.API) http.HandlerFunc { soc, err := strconv.ParseInt(vars["value"], 10, 32) if err == nil { - err = lp.SetTargetSoC(int(soc)) - } - - if err != nil { + lp.SetTargetSoC(int(soc)) + } else { jsonError(w, http.StatusBadRequest, err) return } @@ -133,10 +131,8 @@ func minSoCHandler(lp loadpoint.API) http.HandlerFunc { soc, err := strconv.ParseInt(vars["value"], 10, 32) if err == nil { - err = lp.SetMinSoC(int(soc)) - } - - if err != nil { + lp.SetMinSoC(int(soc)) + } else { jsonError(w, http.StatusBadRequest, err) return } diff --git a/server/mqtt.go b/server/mqtt.go index 5323bf419..7669f75d4 100644 --- a/server/mqtt.go +++ b/server/mqtt.go @@ -71,12 +71,12 @@ func (m *MQTT) listenSetters(topic string, apiHandler loadpoint.API) { }) m.Handler.ListenSetter(topic+"/minSoC/set", func(payload string) { if soc, err := strconv.Atoi(payload); err == nil { - _ = apiHandler.SetMinSoC(soc) + apiHandler.SetMinSoC(soc) } }) m.Handler.ListenSetter(topic+"/targetSoC/set", func(payload string) { if soc, err := strconv.Atoi(payload); err == nil { - _ = apiHandler.SetTargetSoC(soc) + apiHandler.SetTargetSoC(soc) } }) m.Handler.ListenSetter(topic+"/minCurrent/set", func(payload string) { diff --git a/vehicle/wrapper/wrapper.go b/vehicle/wrapper/wrapper.go index 3ea04e083..b41d58898 100644 --- a/vehicle/wrapper/wrapper.go +++ b/vehicle/wrapper/wrapper.go @@ -35,7 +35,7 @@ func (v *Wrapper) Identifiers() []string { return nil } -// OnIdentified returns the identify action +// OnIdentified implements the api.Vehicle interface func (v *Wrapper) OnIdentified() api.ActionConfig { return api.ActionConfig{} }