257 lines
5.3 KiB
Go
257 lines
5.3 KiB
Go
package provider
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/evcc-io/evcc/util/modbus"
|
|
gridx "github.com/grid-x/modbus"
|
|
)
|
|
|
|
// Modbus implements modbus RTU and TCP access
|
|
type Modbus struct {
|
|
log *util.Logger
|
|
conn *modbus.Connection
|
|
reg modbus.Register
|
|
scale float64
|
|
}
|
|
|
|
func init() {
|
|
registry.AddCtx("modbus", NewModbusFromConfig)
|
|
}
|
|
|
|
// NewModbusFromConfig creates Modbus plugin
|
|
func NewModbusFromConfig(ctx context.Context, other map[string]interface{}) (Provider, error) {
|
|
cc := struct {
|
|
modbus.Settings `mapstructure:",squash"`
|
|
Register modbus.Register
|
|
Scale float64
|
|
Delay time.Duration
|
|
ConnectDelay time.Duration
|
|
Timeout time.Duration
|
|
}{
|
|
Scale: 1,
|
|
}
|
|
|
|
if err := util.DecodeOther(other, &cc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
modbus.Lock()
|
|
defer modbus.Unlock()
|
|
|
|
conn, err := modbus.NewConnection(cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Settings.Protocol(), cc.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// set non-default timeout
|
|
conn.Timeout(cc.Timeout)
|
|
|
|
// set non-default delay
|
|
conn.Delay(cc.Delay)
|
|
|
|
// set non-default connect delay
|
|
conn.ConnectDelay(cc.ConnectDelay)
|
|
|
|
log := contextLogger(ctx, util.NewLogger("modbus"))
|
|
conn.Logger(log.TRACE)
|
|
|
|
if err := cc.Register.Error(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mb := &Modbus{
|
|
log: log,
|
|
conn: conn,
|
|
reg: cc.Register,
|
|
scale: cc.Scale,
|
|
}
|
|
return mb, nil
|
|
}
|
|
|
|
func (m *Modbus) readBytes(op modbus.RegisterOperation) ([]byte, error) {
|
|
switch op.FuncCode {
|
|
case gridx.FuncCodeReadHoldingRegisters:
|
|
return m.conn.ReadHoldingRegisters(op.Addr, op.Length)
|
|
|
|
case gridx.FuncCodeReadInputRegisters:
|
|
return m.conn.ReadInputRegisters(op.Addr, op.Length)
|
|
|
|
case gridx.FuncCodeReadCoils:
|
|
return m.conn.ReadCoils(op.Addr, op.Length)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("invalid read function code: %d", op.FuncCode)
|
|
}
|
|
}
|
|
|
|
var _ FloatProvider = (*Modbus)(nil)
|
|
|
|
// FloatGetter implements func() (float64, error)
|
|
func (m *Modbus) FloatGetter() (func() (f float64, err error), error) {
|
|
op, err := m.reg.Operation()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decode, err := m.reg.DecodeFunc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func() (float64, error) {
|
|
bytes, err := m.readBytes(op)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("read failed: %w", err)
|
|
}
|
|
|
|
return m.scale * decode(bytes), nil
|
|
}, nil
|
|
}
|
|
|
|
var _ IntProvider = (*Modbus)(nil)
|
|
|
|
// IntGetter implements IntProvider
|
|
func (m *Modbus) IntGetter() (func() (int64, error), error) {
|
|
g, err := m.FloatGetter()
|
|
|
|
return func() (int64, error) {
|
|
res, err := g()
|
|
return int64(math.Round(res)), err
|
|
}, err
|
|
}
|
|
|
|
var _ StringProvider = (*Modbus)(nil)
|
|
|
|
// StringGetter implements StringProvider
|
|
func (m *Modbus) StringGetter() (func() (string, error), error) {
|
|
op, err := m.reg.Operation()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func() (string, error) {
|
|
b, err := m.readBytes(op)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSpace(string(bytes.TrimLeft(b, "\x00"))), nil
|
|
}, nil
|
|
}
|
|
|
|
var _ BoolProvider = (*Modbus)(nil)
|
|
|
|
// BoolGetter implements BoolProvider
|
|
func (m *Modbus) BoolGetter() (func() (bool, error), error) {
|
|
g, err := m.FloatGetter()
|
|
|
|
return func() (bool, error) {
|
|
res, err := g()
|
|
return res != 0, err
|
|
}, err
|
|
}
|
|
|
|
func (m *Modbus) writeFunc() (func(float64) error, error) {
|
|
op, err := m.reg.Operation()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encode, err := m.reg.EncodeFunc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func(val float64) error {
|
|
val *= m.scale
|
|
|
|
switch op.FuncCode {
|
|
case gridx.FuncCodeWriteSingleCoil:
|
|
var uval uint16
|
|
if val != 0 {
|
|
uval = 0xFF00
|
|
}
|
|
_, err = m.conn.WriteSingleCoil(op.Addr, uval)
|
|
return err
|
|
|
|
case gridx.FuncCodeWriteSingleRegister:
|
|
_, err = m.conn.WriteSingleRegister(op.Addr, uint16(val))
|
|
return err
|
|
|
|
case gridx.FuncCodeWriteMultipleRegisters:
|
|
b, err := encode(val)
|
|
if err == nil {
|
|
_, err = m.conn.WriteMultipleRegisters(op.Addr, op.Length, b)
|
|
}
|
|
return err
|
|
|
|
default:
|
|
return fmt.Errorf("invalid func code: %d", op.FuncCode)
|
|
}
|
|
}, nil
|
|
}
|
|
|
|
var _ SetFloatProvider = (*Modbus)(nil)
|
|
|
|
// FloatSetter implements SetFloatProvider
|
|
func (m *Modbus) FloatSetter(_ string) (func(float64) error, error) {
|
|
return m.writeFunc()
|
|
}
|
|
|
|
var _ SetIntProvider = (*Modbus)(nil)
|
|
|
|
// IntSetter implements SetIntProvider
|
|
func (m *Modbus) IntSetter(_ string) (func(int64) error, error) {
|
|
fun, err := m.writeFunc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func(val int64) error {
|
|
return fun(float64(val))
|
|
}, nil
|
|
}
|
|
|
|
var _ SetBoolProvider = (*Modbus)(nil)
|
|
|
|
// BoolSetter implements SetBoolProvider
|
|
func (m *Modbus) BoolSetter(param string) (func(bool) error, error) {
|
|
set, err := m.IntSetter(param)
|
|
|
|
return func(val bool) error {
|
|
var ival int64
|
|
if val {
|
|
ival = 1
|
|
}
|
|
|
|
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
|
|
}
|