Sonnenbatterie 5/6: add battery control (#21152)

This commit is contained in:
ngehrsitz 2025-05-13 22:45:38 +02:00 • committed by GitHub
parent d14c9ee49b
commit 34cf433b1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 149 additions and 18 deletions

61
plugin/sleep.go Normal file
View file

@ -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
}

View file

@ -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)

View file

@ -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 }}