Skip to content

Commit

Permalink
Improve action handling to consistently apply and reset vehicle setti…
Browse files Browse the repository at this point in the history
…ngs (#1942)

allow GetTargetSoC() and GetMinSoC() to be used even when no vehicle is connected
use SetMinSoC() and SetTargetSoC() functions in applyAction()
allow to set minSoC to 0 with applyAction()
apply OnIdentify actions only when OnIdentify node is present in vehicle configuration (optional parameter)
  • Loading branch information
rivengh committed Dec 1, 2021
1 parent d9c4cc5 commit a45e240
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 76 deletions.
25 changes: 19 additions & 6 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down
11 changes: 6 additions & 5 deletions cmd/dumper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
}
70 changes: 32 additions & 38 deletions core/loadpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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,
}

Expand All @@ -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)
}
}

Expand Down Expand Up @@ -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)
}
if action.MinCurrent > 0 {
lp.SetMinCurrent(action.MinCurrent)
}
if action.MaxCurrent > 0 {
lp.SetMaxCurrent(action.MaxCurrent)
}
if action.MinSoC != 0 {
// TODO deduplicate with SetMinSoC
lp.Lock()
lp.SoC.Min = action.MinSoC
lp.publish("minSoC", action.MinSoC)
lp.Unlock()
}
if action.TargetSoC != 0 {
// TODO deduplicate with SetTargetSoC
lp.Lock()
lp.SoC.Target = action.TargetSoC
lp.publish("targetSoC", action.TargetSoC)
lp.Unlock()
func (lp *LoadPoint) applyAction(actionCfg api.ActionConfig) {
if actionCfg.Mode != nil {
lp.SetMode(*actionCfg.Mode)
}
if actionCfg.MinCurrent != nil {
lp.SetMinCurrent(*actionCfg.MinCurrent)
}
if actionCfg.MaxCurrent != nil {
lp.SetMaxCurrent(*actionCfg.MaxCurrent)
}
if actionCfg.MinSoC != nil {
lp.SetMinSoC(*actionCfg.MinSoC)
}
if actionCfg.TargetSoC != nil {
lp.SetTargetSoC(*actionCfg.TargetSoC)
}
}

Expand Down
4 changes: 2 additions & 2 deletions core/loadpoint/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 2 additions & 14 deletions core/loadpoint_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -100,8 +90,6 @@ func (lp *LoadPoint) SetMinSoC(soc int) error {
lp.publish("minSoC", soc)
lp.requestUpdate()
}

return nil
}

// GetPhases returns loadpoint enabled phases
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
12 changes: 4 additions & 8 deletions server/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions server/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion vehicle/wrapper/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand Down

0 comments on commit a45e240

Please sign in to comment.