diff --git a/plugin/sleep.go b/plugin/sleep.go new file mode 100644 index 000000000..2bba311b3 --- /dev/null +++ b/plugin/sleep.go @@ -0,0 +1,61 @@ +package plugin + +import ( + "context" + "time" + + "github.com/evcc-io/evcc/util" +) + +type sleepPlugin struct { + duration time.Duration +} + +func init() { + registry.AddCtx("sleep", NewSleepFromConfig) +} + +// NewSleepFromConfig creates sleep provider +func NewSleepFromConfig(ctx context.Context, other map[string]interface{}) (Plugin, error) { + var cc struct { + Duration time.Duration + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + o := &sleepPlugin{ + duration: cc.Duration, + } + + return o, nil +} + +// sleeper is the generic sleeper function for sleepPlugin +// it is currently not possible to write this as a method +func sleeper[T comparable](o *sleepPlugin) func(T) error { + return func(val T) error { + <-time.After(o.duration) + + return nil + } +} + +var _ IntSetter = (*sleepPlugin)(nil) + +func (o *sleepPlugin) IntSetter(param string) (func(int64) error, error) { + return sleeper[int64](o), nil +} + +var _ FloatSetter = (*sleepPlugin)(nil) + +func (o *sleepPlugin) FloatSetter(param string) (func(float64) error, error) { + return sleeper[float64](o), nil +} + +var _ BoolSetter = (*sleepPlugin)(nil) + +func (o *sleepPlugin) BoolSetter(param string) (func(bool) error, error) { + return sleeper[bool](o), nil +} diff --git a/plugin/watchdog.go b/plugin/watchdog.go index e5b6f1166..308efa377 100644 --- a/plugin/watchdog.go +++ b/plugin/watchdog.go @@ -2,6 +2,8 @@ package plugin import ( "context" + "fmt" + "slices" "strconv" "sync" "time" @@ -13,7 +15,7 @@ type watchdogPlugin struct { mu sync.Mutex ctx context.Context log *util.Logger - reset *string + reset []string initial *string set Config timeout time.Duration @@ -27,7 +29,7 @@ func init() { // NewWatchDogFromConfig creates watchDog provider func NewWatchDogFromConfig(ctx context.Context, other map[string]interface{}) (Plugin, error) { var cc struct { - Reset *string + Reset []string Initial *string Set Config Timeout time.Duration @@ -64,7 +66,7 @@ func (o *watchdogPlugin) wdt(ctx context.Context, set func() error) { // setter is the generic setter function for watchdogPlugin // it is currently not possible to write this as a method -func setter[T comparable](o *watchdogPlugin, set func(T) error, reset *T) func(T) error { +func setter[T comparable](o *watchdogPlugin, set func(T) error, reset []T) func(T) error { return func(val T) error { o.mu.Lock() @@ -75,7 +77,7 @@ func setter[T comparable](o *watchdogPlugin, set func(T) error, reset *T) func(T } // start wdt on non-reset value - if reset == nil || val != *reset { + if !slices.Contains(reset, val) { var ctx context.Context ctx, o.cancel = context.WithCancel(context.Background()) @@ -98,13 +100,15 @@ func (o *watchdogPlugin) IntSetter(param string) (func(int64) error, error) { return nil, err } - var reset *int64 + var reset []int64 if o.reset != nil { - val, err := strconv.ParseInt(*o.reset, 10, 64) - if err != nil { - return nil, err + for _, v := range o.reset { + val, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, err + } + reset = append(reset, val) } - reset = &val } res := setter(o, set, reset) @@ -130,13 +134,15 @@ func (o *watchdogPlugin) FloatSetter(param string) (func(float64) error, error) return nil, err } - var reset *float64 + var reset []float64 if o.reset != nil { - val, err := strconv.ParseFloat(*o.reset, 64) - if err != nil { - return nil, err + for _, v := range o.reset { + val, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil, err + } + reset = append(reset, val) } - reset = &val } res := setter(o, set, reset) @@ -162,13 +168,15 @@ func (o *watchdogPlugin) BoolSetter(param string) (func(bool) error, error) { return nil, err } - var reset *bool - if o.reset != nil { - val, err := strconv.ParseBool(*o.reset) + var reset []bool + if len(o.reset) > 1 { + return nil, fmt.Errorf("more than one boolean reset value") + } else if len(o.reset) == 1 { + val, err := strconv.ParseBool(o.reset[0]) if err != nil { return nil, err } - reset = &val + reset = append(reset, val) } res := setter(o, set, reset) diff --git a/templates/definition/meter/sonnenbatterie_eco56.yaml b/templates/definition/meter/sonnenbatterie_eco56.yaml index a5c7d550a..0d5bc3049 100644 --- a/templates/definition/meter/sonnenbatterie_eco56.yaml +++ b/templates/definition/meter/sonnenbatterie_eco56.yaml @@ -11,42 +11,104 @@ params: default: 7979 - name: capacity advanced: true + - name: maxchargepower + advanced: true + - name: cache + advanced: true + default: 5s render: | type: custom {{- if eq .usage "grid" }} power: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M39 - .M38 # current purchase - current feed-in at the interconnection point energy: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M41 # cumulated purchase since installation {{- end }} {{- if eq .usage "pv" }} power: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M03 # current pv power energy: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M37 # cumulated pv production since installation of Sonnenbatterie {{- end }} {{- if eq .usage "battery" }} power: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} # M34 current discharging power, S65 max inverter power # M35 current charging power, S65 max inverter power jq: (if .M34 <= .S65 then .M34 else 0 end) - (if .M35 <= .S65 then .M35 else 0 end) energy: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M31 # total stored energy over lifetime soc: source: http uri: http://{{ .host }}:{{ .port }}/rest/devices/battery + cache: {{ .cache }} jq: .M30 # SOC relative to usable capacity (.M05 # display SOC) + batterymode: + # use a sequence to propagate resets to the watchdog + source: sequence + set: + - source: switch + switch: + - case: 1 # normal + set: + source: http + method: PUT + uri: http://{{ .host }}:{{ .port }}/rest/devices/battery/C06 + body: '10' # Automatic + - case: 2 # hold + set: + source: http + method: PUT + uri: http://{{ .host }}:{{ .port }}/rest/devices/battery/C06 + body: '20' # Standby + - case: 3 # charge + set: + source: http + method: PUT + uri: http://{{ .host }}:{{ .port }}/rest/devices/battery/C06 + body: '55' # slave mode + # run the watchdog only on the charging power request to avoid making unnecessary mode changes + - source: watchdog + timeout: 30s # 3 minutes without setting a value will stop all charging, 30s was chosen to account for api instability + reset: [1,2] + set: + source: switch + switch: + - case: 1 # normal + set: + source: sleep + duration: 0s + - case: 2 # hold + set: + source: sleep + duration: 0s + # only charging requires repeated requests + - case: 3 # charge + set: + source: sequence + set: + - source: sleep + duration: 1s + - source: http + method: PUT + uri: http://{{ .host }}:{{ .port }}/rest/devices/battery/C24 + body: {{ if .maxchargepower }}{{ .maxchargepower }}{{ else }}99000{{ end }} capacity: {{ .capacity }} # kWh {{- end }}