From 11d2b8b85d1095fcae8c2b4c1b2eefaaa990b840 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 12 Oct 2024 11:09:12 +0200 Subject: [PATCH] Modbus: add bytes setter (#16510) --- provider/config.go | 13 ++++++ provider/const.go | 22 ++++++++++ provider/convert.go | 42 ++++++++++++------- provider/modbus.go | 21 ++++++++++ templates/definition/meter/sofarsolar-g3.yaml | 37 +++++----------- util/modbus/register.go | 2 + 6 files changed, 97 insertions(+), 40 deletions(-) diff --git a/provider/config.go b/provider/config.go index 7e7d025f0..eedba5ed6 100644 --- a/provider/config.go +++ b/provider/config.go @@ -36,6 +36,9 @@ type ( SetBoolProvider interface { BoolSetter(param string) (func(bool) error, error) } + SetBytesProvider interface { + BytesSetter(param string) (func([]byte) error, error) + } ) // Config is the general provider config @@ -144,3 +147,13 @@ func NewBoolSetterFromConfig(ctx context.Context, param string, config Config) ( return prov.BoolSetter(param) } + +// NewBytesSetterFromConfig creates a BytesSetter from config +func NewBytesSetterFromConfig(param string, config Config) (func([]byte) error, error) { + prov, err := provider[SetBytesProvider]("bytes", config) + if err != nil { + return nil, err + } + + return prov.BytesSetter(param) +} diff --git a/provider/const.go b/provider/const.go index 2c58bb2ac..0dcaefb93 100644 --- a/provider/const.go +++ b/provider/const.go @@ -2,7 +2,9 @@ package provider import ( "context" + "encoding/hex" "strconv" + "strings" "github.com/evcc-io/evcc/provider/pipeline" "github.com/evcc-io/evcc/util" @@ -149,3 +151,23 @@ func (o *constProvider) BoolSetter(param string) (func(bool) error, error) { return set(val) }, err } + +var _ SetBytesProvider = (*constProvider)(nil) + +func (o *constProvider) BytesSetter(param string) (func([]byte) error, error) { + set, err := NewBytesSetterFromConfig(param, o.set) + if err != nil { + return nil, err + } + + str := strings.ReplaceAll(strings.TrimPrefix(o.str, "0x"), "_", "") + + val, err := hex.DecodeString(str) + if err != nil { + err = nil + } + + return func(_ []byte) error { + return set(val) + }, err +} diff --git a/provider/convert.go b/provider/convert.go index d26932043..0eedb00d2 100644 --- a/provider/convert.go +++ b/provider/convert.go @@ -2,6 +2,7 @@ package provider import ( "context" + "encoding/binary" "fmt" "github.com/evcc-io/evcc/util" @@ -33,27 +34,40 @@ func NewConvertFromConfig(ctx context.Context, other map[string]interface{}) (Pr var _ SetFloatProvider = (*convertProvider)(nil) func (o *convertProvider) FloatSetter(param string) (func(float64) error, error) { - if o.Convert != "float2int" { + switch o.Convert { + case "float2int": + set, err := NewIntSetterFromConfig(o.ctx, param, o.Set) + + return func(val float64) error { + return set(int64(val)) + }, err + + default: return nil, fmt.Errorf("convert: invalid conversion: %s", o.Convert) } - - set, err := NewIntSetterFromConfig(o.ctx, param, o.Set) - - return func(val float64) error { - return set(int64(val)) - }, err } var _ SetIntProvider = (*convertProvider)(nil) func (o *convertProvider) IntSetter(param string) (func(int64) error, error) { - if o.Convert != "int2float" { + switch o.Convert { + case "int2float": + set, err := NewFloatSetterFromConfig(o.ctx, param, o.Set) + + return func(val int64) error { + return set(float64(val)) + }, err + + case "int2bytes": + set, err := NewBytesSetterFromConfig(o.ctx, param, o.Set) + + return func(val int64) error { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(val)) + return set(b) + }, err + + default: return nil, fmt.Errorf("convert: invalid conversion: %s", o.Convert) } - - set, err := NewFloatSetterFromConfig(o.ctx, param, o.Set) - - return func(val int64) error { - return set(float64(val)) - }, err } diff --git a/provider/modbus.go b/provider/modbus.go index 0dd97eeab..9ec5c3260 100644 --- a/provider/modbus.go +++ b/provider/modbus.go @@ -234,3 +234,24 @@ func (m *Modbus) BoolSetter(param string) (func(bool) error, error) { return set(ival) }, err } + +var _ SetBytesProvider = (*Modbus)(nil) + +// BytesSetter implements SetBytesProvider +func (m *Modbus) BytesSetter(_ string) (func([]byte) error, error) { + op, err := m.reg.Operation() + if err != nil { + return nil, err + } + + return func(val []byte) error { + switch op.FuncCode { + case gridx.FuncCodeWriteMultipleRegisters: + _, err = m.conn.WriteMultipleRegisters(op.Addr, uint16(len(val)/2), val) + return err + + default: + return fmt.Errorf("invalid func code: %d", op.FuncCode) + } + }, nil +} diff --git a/templates/definition/meter/sofarsolar-g3.yaml b/templates/definition/meter/sofarsolar-g3.yaml index ca87dfd86..b11632e9d 100644 --- a/templates/definition/meter/sofarsolar-g3.yaml +++ b/templates/definition/meter/sofarsolar-g3.yaml @@ -171,33 +171,18 @@ render: | address: 0x1110 type: writemultiple decode: int16 - - source: const - value: 0 + - source: convert + convert: int2bytes set: - source: modbus - {{- include "modbus" . | indent 10 }} - register: - address: 0x1187 - type: writemultiple - decode: int32 - - source: const - value: 0 - set: - source: modbus - {{- include "modbus" . | indent 10 }} - register: - address: 0x1189 - type: writemultiple - decode: int32 - - source: const - value: 0x7FFFFFFF - set: - source: modbus - {{- include "modbus" . | indent 10 }} - register: - address: 0x118B - type: writemultiple - decode: int32 + source: const + value: '0x00000000_00000000_7FFFFFFF' + set: + source: modbus + {{- include "modbus" . | indent 12 }} + register: + address: 0x1187 + type: writemultiple + decode: bytes - case: 3 # charge (not implemented -> normal) set: source: const diff --git a/util/modbus/register.go b/util/modbus/register.go index d2aade888..4b2f3218a 100644 --- a/util/modbus/register.go +++ b/util/modbus/register.go @@ -50,6 +50,8 @@ func (r Register) Length() (uint16, error) { return 2, nil case strings.Contains(enc, "64"): return 4, nil + case strings.EqualFold(enc, "bytes"): + return 0, nil default: return 0, fmt.Errorf("invalid register length: %s", enc) }