Skip to content

Commit

Permalink
Add remaining modbus getters and setters (evcc-io#550)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Dec 31, 2020
1 parent 133c8c3 commit 6f0fb94
Showing 3 changed files with 107 additions and 16 deletions.
2 changes: 1 addition & 1 deletion charger/charger.go
Original file line number Diff line number Diff line change
@@ -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)
116 changes: 101 additions & 15 deletions provider/modbus.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 5 additions & 0 deletions util/modbus/modbus.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 6f0fb94

Please sign in to comment.