Files
koolnova2mqtt/modbus/modbus.go
2020-12-22 01:22:00 +01:00

112 lines
2.4 KiB
Go

package modbus
import (
"encoding/binary"
"errors"
"log"
"sync"
"time"
gmodbus "github.com/wz2b/modbus"
)
type Config struct {
Port string
BaudRate int
DataBits int
Parity string
StopBits int
Timeout time.Duration
}
type Modbus struct {
handler *gmodbus.RTUClientHandler
client gmodbus.Client
lock sync.RWMutex
}
func throttle(ms int) {
time.Sleep(time.Duration(ms) * time.Millisecond)
}
var ErrIncorrectResultSize = errors.New("Incorrect number of results returned")
func New(config *Config) (*Modbus, error) {
handler := gmodbus.NewRTUClientHandler(config.Port)
handler.BaudRate = config.BaudRate
handler.DataBits = config.DataBits
handler.Parity = config.Parity
handler.StopBits = config.StopBits
handler.Timeout = config.Timeout
return &Modbus{
handler: handler,
client: gmodbus.NewClient(handler),
}, handler.Connect()
}
func (mb *Modbus) Close() error {
return mb.handler.Close()
}
func parseResults(r []byte, quantity uint16) ([]uint16, error) {
if len(r) != int(quantity*2) {
return nil, ErrIncorrectResultSize
}
results := make([]uint16, quantity)
for n := uint16(0); n < quantity; n++ {
results[n] = binary.BigEndian.Uint16(r[n*2 : n*2+2])
}
return results, nil
}
func (mb *Modbus) ReadRegister(slaveID byte, address uint16, quantity uint16) (results []uint16, err error) {
err = mb.try(slaveID, func() (err error) {
r, err := mb.client.ReadHoldingRegisters(address-1, quantity)
if err != nil {
return err
}
results, err = parseResults(r, quantity)
return err
})
return results, err
}
func (mb *Modbus) WriteRegister(slaveID byte, address uint16, value uint16) (results []uint16, err error) {
err = mb.try(slaveID, func() (err error) {
r, err := mb.client.WriteSingleRegister(address-1, value)
if err != nil {
return err
}
results, err = parseResults(r, 1)
return err
})
return results, err
}
func (mb *Modbus) try(slaveID byte, f func() error) (err error) {
mb.lock.Lock()
defer mb.lock.Unlock()
defer throttle(100)
mb.handler.SlaveId = slaveID
retries := 5
delay := 100
for retries > 0 {
err = f()
if err == nil {
return nil
}
log.Printf("Retried modbus operation due to %s. %d retries left\n", err, retries)
mb.handler.Close()
throttle(100)
connectErr := mb.handler.Connect()
if connectErr != nil {
return connectErr
}
retries--
throttle(delay)
delay *= 2
}
return err
}