Improve action handling to consistently apply and reset vehicle settings (#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)
This commit is contained in:
parent
d9c4cc5108
commit
a45e240253
10 changed files with 67 additions and 72 deletions
25
api/api.go
25
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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
go.mod
1
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
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue