diff --git a/charger/charger.go b/charger/charger.go index a3527e9bec..8cfea0bae1 100644 --- a/charger/charger.go +++ b/charger/charger.go @@ -40,7 +40,7 @@ func NewConfigurableFromConfig(other map[string]interface{}) (api.Charger, error status, err := provider.NewStringGetterFromConfig(cc.Status) if err != nil { - return nil, err + return nil, fmt.Errorf("status: %w", err) } enabled, err := provider.NewBoolGetterFromConfig(cc.Enabled) diff --git a/provider/modbus.go b/provider/modbus.go index dc4567ebb4..5820dbd785 100644 --- a/provider/modbus.go +++ b/provider/modbus.go @@ -1,9 +1,11 @@ package provider import ( + "encoding/binary" "errors" "fmt" "math" + "strings" "github.com/andig/evcc/util" "github.com/andig/evcc/util/modbus" @@ -115,9 +117,19 @@ func NewModbusFromConfig(other map[string]interface{}) (IntProvider, error) { return mb, nil } -// FloatGetter executes configured modbus read operation and implements func() (float64, error) -func (m *Modbus) FloatGetter() func() (float64, error) { - return m.floatGetter +func (m *Modbus) bytesGetter() ([]byte, error) { + if op := m.op.MBMD; op.FuncCode != 0 { + switch op.FuncCode { + case rs485.ReadHoldingReg: + return m.conn.ReadHoldingRegisters(op.OpCode, op.ReadLen) + case rs485.ReadInputReg: + return m.conn.ReadInputRegisters(op.OpCode, op.ReadLen) + default: + return nil, fmt.Errorf("unknown function code %d", op.FuncCode) + } + } + + return nil, errors.New("expected rtu reading") } func (m *Modbus) floatGetter() (float64, error) { @@ -127,17 +139,8 @@ func (m *Modbus) floatGetter() (float64, error) { // if funccode is configured, execute the read directly if op := m.op.MBMD; op.FuncCode != 0 { var bytes []byte - switch op.FuncCode { - case rs485.ReadHoldingReg: - bytes, err = m.conn.ReadHoldingRegisters(op.OpCode, op.ReadLen) - case rs485.ReadInputReg: - bytes, err = m.conn.ReadInputRegisters(op.OpCode, op.ReadLen) - default: - return 0, fmt.Errorf("unknown function code %d", op.FuncCode) - } - - if err != nil { - return 0, fmt.Errorf("read failed: %v", err) + if bytes, err = m.bytesGetter(); err != nil { + return 0, fmt.Errorf("read failed: %w", err) } return m.scale * op.Transform(bytes), nil @@ -175,7 +178,12 @@ func (m *Modbus) floatGetter() (float64, error) { return m.scale * res.Value, err } -// IntGetter executes configured modbus read operation and implements provider.IntGetter +// FloatGetter executes configured modbus read operation and implements func() (float64, error) +func (m *Modbus) FloatGetter() func() (float64, error) { + return m.floatGetter +} + +// IntGetter executes configured modbus read operation and implements IntProvider func (m *Modbus) IntGetter() func() (int64, error) { g := m.FloatGetter() @@ -184,3 +192,81 @@ func (m *Modbus) IntGetter() func() (int64, error) { return int64(math.Round(res)), err } } + +// StringGetter executes configured modbus read operation and implements IntProvider +func (m *Modbus) StringGetter() func() (string, error) { + return func() (string, error) { + bytes, err := m.bytesGetter() + if err != nil { + return "", err + } + + return strings.TrimSpace(string(bytes)), nil + } +} + +// UintFromBytes converts byte slice to bigendian uint value +func UintFromBytes(bytes []byte) (u uint64, err error) { + switch l := len(bytes); l { + case 2: + u = uint64(binary.BigEndian.Uint16(bytes)) + case 4: + u = uint64(binary.BigEndian.Uint32(bytes)) + case 8: + u = binary.BigEndian.Uint64(bytes) + default: + err = fmt.Errorf("unexpected length: %d", l) + } + + return u, err +} + +// BoolGetter executes configured modbus read operation and implements IntProvider +func (m *Modbus) BoolGetter() func() (bool, error) { + return func() (bool, error) { + bytes, err := m.bytesGetter() + if err != nil { + return false, err + } + + u, err := UintFromBytes(bytes) + return u > 0, err + } +} + +// IntSetter executes configured modbus write operation and implements SetIntProvider +func (m *Modbus) IntSetter(param string) func(int64) error { + return func(val int64) error { + var err error + + // if funccode is configured, execute the read directly + if op := m.op.MBMD; op.FuncCode != 0 { + uval := uint16(int64(m.scale) * val) + + switch op.FuncCode { + case modbus.WriteSingleRegister: + _, err = m.conn.WriteSingleRegister(op.OpCode, uval) + default: + err = fmt.Errorf("unknown function code %d", op.FuncCode) + } + } else { + err = errors.New("modbus plugin does not support writing to sunspec") + } + + return err + } +} + +// BoolSetter executes configured modbus write operation and implements SetBoolProvider +func (m *Modbus) BoolSetter(param string) func(bool) error { + set := m.IntSetter(param) + + return func(val bool) error { + var ival int64 + if val { + ival = 1 + } + + return set(ival) + } +} diff --git a/util/modbus/modbus.go b/util/modbus/modbus.go index 77104a8f32..ab245eb6b0 100644 --- a/util/modbus/modbus.go +++ b/util/modbus/modbus.go @@ -13,6 +13,9 @@ import ( "github.com/volkszaehler/mbmd/meters/sunspec" ) +// WriteSingleRegister 16-bit wise write access +const WriteSingleRegister = 6 // modbus.FuncCodeWriteSingleRegister + // Settings contains the ModBus settings type Settings struct { ID uint8 @@ -225,6 +228,8 @@ func RegisterOperation(r Register) (rs485.Operation, error) { op.FuncCode = rs485.ReadHoldingReg case "input": op.FuncCode = rs485.ReadInputReg + case "writesingle": + op.FuncCode = WriteSingleRegister // modbus.FuncCodeWriteSingleRegister default: return rs485.Operation{}, fmt.Errorf("invalid register type: %s", r.Type) }