From bf36704b06966b4ff6010f37dee6a1fec0acf9da Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 21 Nov 2023 10:47:11 +0100 Subject: [PATCH] Sungrow: add battery control (#10833) --- provider/const.go | 54 +++++++++++ provider/map.go | 2 +- provider/sequence.go | 96 +++++++++++++++++++ provider/switch.go | 81 ++++++++++++++++ .../definition/meter/sungrow-hybrid.yaml | 76 +++++++++++++++ 5 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 provider/sequence.go create mode 100644 provider/switch.go diff --git a/provider/const.go b/provider/const.go index 69bf6bc7f..17304f037 100644 --- a/provider/const.go +++ b/provider/const.go @@ -9,6 +9,7 @@ import ( type constProvider struct { str string + set Config } func init() { @@ -20,6 +21,7 @@ func NewConstFromConfig(other map[string]interface{}) (Provider, error) { var cc struct { Value string pipeline.Settings `mapstructure:",squash"` + Set Config } if err := util.DecodeOther(other, &cc); err != nil { @@ -38,6 +40,7 @@ func NewConstFromConfig(other map[string]interface{}) (Provider, error) { o := &constProvider{ str: string(b), + set: cc.Set, } return o, nil @@ -68,3 +71,54 @@ func (o *constProvider) FloatGetter() (func() (float64, error), error) { return val, err }, err } + +var _ BoolProvider = (*constProvider)(nil) + +func (o *constProvider) BoolGetter() (func() (bool, error), error) { + val, err := strconv.ParseBool(o.str) + return func() (bool, error) { + return val, err + }, err +} + +var _ SetIntProvider = (*constProvider)(nil) + +func (o *constProvider) IntSetter(param string) (func(int64) error, error) { + set, err := NewIntSetterFromConfig(param, o.set) + if err != nil { + return nil, err + } + + val, err := strconv.ParseInt(o.str, 10, 64) + return func(_ int64) error { + return set(val) + }, err +} + +var _ SetFloatProvider = (*constProvider)(nil) + +func (o *constProvider) FloatSetter(param string) (func(float64) error, error) { + set, err := NewFloatSetterFromConfig(param, o.set) + if err != nil { + return nil, err + } + + val, err := strconv.ParseFloat(o.str, 64) + return func(_ float64) error { + return set(val) + }, err +} + +var _ SetBoolProvider = (*constProvider)(nil) + +func (o *constProvider) BoolSetter(param string) (func(bool) error, error) { + set, err := NewBoolSetterFromConfig(param, o.set) + if err != nil { + return nil, err + } + + val, err := strconv.ParseBool(o.str) + return func(_ bool) error { + return set(val) + }, err +} diff --git a/provider/map.go b/provider/map.go index 6597efd25..2e7140c9e 100644 --- a/provider/map.go +++ b/provider/map.go @@ -46,7 +46,7 @@ func (o *mapProvider) IntSetter(param string) (func(int64) error, error) { return func(val int64) error { m, ok := o.values[val] if !ok { - return fmt.Errorf("value %d not found", val) + return fmt.Errorf("map: value not found: %d", val) } return set(m) }, err diff --git a/provider/sequence.go b/provider/sequence.go new file mode 100644 index 000000000..4e358d3e2 --- /dev/null +++ b/provider/sequence.go @@ -0,0 +1,96 @@ +package provider + +import ( + "github.com/evcc-io/evcc/util" +) + +type sequenceProvider struct { + set []Config +} + +func init() { + registry.Add("sequence", NewSequenceFromConfig) +} + +// NewSequenceFromConfig creates sequence provider +func NewSequenceFromConfig(other map[string]interface{}) (Provider, error) { + var cc struct { + Set []Config + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + o := &sequenceProvider{ + set: cc.Set, + } + + return o, nil +} + +var _ SetIntProvider = (*sequenceProvider)(nil) + +func (o *sequenceProvider) IntSetter(param string) (func(int64) error, error) { + set := make([]func(int64) error, 0, len(o.set)) + for _, cc := range o.set { + s, err := NewIntSetterFromConfig(param, cc) + if err != nil { + return nil, err + } + set = append(set, s) + } + + return func(val int64) error { + for _, s := range set { + if err := s(val); err != nil { + return err + } + } + return nil + }, nil +} + +var _ SetFloatProvider = (*sequenceProvider)(nil) + +func (o *sequenceProvider) FloatSetter(param string) (func(float64) error, error) { + set := make([]func(float64) error, 0, len(o.set)) + for _, cc := range o.set { + s, err := NewFloatSetterFromConfig(param, cc) + if err != nil { + return nil, err + } + set = append(set, s) + } + + return func(val float64) error { + for _, s := range set { + if err := s(val); err != nil { + return err + } + } + return nil + }, nil +} + +var _ SetBoolProvider = (*sequenceProvider)(nil) + +func (o *sequenceProvider) BoolSetter(param string) (func(bool) error, error) { + set := make([]func(bool) error, 0, len(o.set)) + for _, cc := range o.set { + s, err := NewBoolSetterFromConfig(param, cc) + if err != nil { + return nil, err + } + set = append(set, s) + } + + return func(val bool) error { + for _, s := range set { + if err := s(val); err != nil { + return err + } + } + return nil + }, nil +} diff --git a/provider/switch.go b/provider/switch.go new file mode 100644 index 000000000..2d736f2a5 --- /dev/null +++ b/provider/switch.go @@ -0,0 +1,81 @@ +package provider + +import ( + "fmt" + "strconv" + + "github.com/evcc-io/evcc/util" +) + +type Case struct { + Case string + Set Config +} + +type switchProvider struct { + cases []Case + dflt *Config +} + +func init() { + registry.Add("switch", NewSwitchFromConfig) +} + +// NewSwitchFromConfig creates switch provider +func NewSwitchFromConfig(other map[string]interface{}) (Provider, error) { + var cc struct { + Switch []Case + Default *Config + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + o := &switchProvider{ + cases: cc.Switch, + dflt: cc.Default, + } + + return o, nil +} + +var _ SetIntProvider = (*switchProvider)(nil) + +func (o *switchProvider) IntSetter(param string) (func(int64) error, error) { + set := make([]func(int64) error, 0, len(o.cases)) + for _, cc := range o.cases { + s, err := NewIntSetterFromConfig(param, cc.Set) + if err != nil { + return nil, err + } + set = append(set, s) + } + + var dflt func(int64) error + if o.dflt != nil { + var err error + if dflt, err = NewIntSetterFromConfig(param, *o.dflt); err != nil { + return nil, err + } + } + + return func(val int64) error { + for i, s := range o.cases { + ival, err := strconv.ParseInt(s.Case, 10, 64) + if err != nil { + return err + } + + if ival == val { + return set[i](val) + } + } + + if dflt != nil { + return dflt(val) + } + + return fmt.Errorf("switch: value not found: %d", val) + }, nil +} diff --git a/templates/definition/meter/sungrow-hybrid.yaml b/templates/definition/meter/sungrow-hybrid.yaml index 756b43ce3..682393897 100644 --- a/templates/definition/meter/sungrow-hybrid.yaml +++ b/templates/definition/meter/sungrow-hybrid.yaml @@ -15,6 +15,7 @@ params: - name: modbus choice: ["rs485", "tcpip"] baudrate: 9600 + - name: timeout - name: capacity advanced: true render: | @@ -97,6 +98,81 @@ render: | type: input decode: int16 scale: 0.1 + batterymode: + source: switch + switch: + - case: 1 # normal + set: + source: sequence + set: + - source: const + value: 0 # self consumption + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13049 # EMS mode + type: writesingle + decode: uint16 + - source: const + value: 0xCC # stop + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13050 # Forced mode + type: writesingle + decode: uint16 + - case: 2 # hold + set: + source: sequence + set: + - source: const + value: 2 # forced mode + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13049 # EMS mode + type: writesingle + decode: uint16 + - source: const + value: 0xCC + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13050 # Forced mode + type: writesingle + decode: uint16 + - case: 2 # charge + set: + source: sequence + set: + - source: const + value: 2 # forced mode + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13049 # EMS mode + type: writesingle + decode: uint16 + - source: const + value: 0xAA + set: + source: modbus + {{- include "modbus" . | indent 10 }} + timeout: {{ .timeout }} + register: + address: 13050 # Forced mode + type: writesingle + decode: uint16 {{- if .capacity }} capacity: {{ .capacity }} # kWh {{- end }}