Files
modbustcpclient/client.go
Henrik Sölver 9a92e47837 Fix lockup on io timeout
When the client got a IO timeout the timer were never reset
Adjusting timeouts
2023-02-24 22:59:39 +01:00

142 lines
3.2 KiB
Go

package modbustcpclient
import (
"encoding/binary"
"fmt"
"io"
"net"
"sync"
"time"
)
type Mbclient struct {
transactionCounter uint16
address string
header [7]byte
unit uint8
conn net.Conn
t *time.Timer
keepAliveDuration time.Duration
wg sync.WaitGroup
}
func New(Address string, Unit uint8, KeepAlive time.Duration) (*Mbclient, error) {
c := new(Mbclient)
c.address = Address
c.unit = Unit
c.t = time.NewTimer(0)
<-c.t.C
c.keepAliveDuration = KeepAlive
return c, nil
}
func (m *Mbclient) closer() {
<-m.t.C
m.conn.Close()
m.wg.Done()
}
func (m *Mbclient) ReadRegisters(first uint16, numRegs uint16) ([]uint16, error) {
var err error
// If The timer is expired, conn is closed and needs to be reopened
if !m.t.Stop() {
// Wait for closer to exit to mitigate race condiion
// between closer routine and this code path
m.wg.Wait()
m.conn, err = net.DialTimeout("tcp", m.address, 5*time.Second)
if err != nil {
return nil, err
}
m.wg.Add(1)
go m.closer()
}
defer m.t.Reset(m.keepAliveDuration)
const requestLength = 12
m.transactionCounter++
req := make([]byte, requestLength)
var responseHeader mbapHeader
var mbpayload mbPDU
binary.BigEndian.PutUint16(req[0:2], m.transactionCounter)
binary.BigEndian.PutUint16(req[2:4], 0)
binary.BigEndian.PutUint16(req[4:6], 6) // Length
req[6] = m.unit
req[7] = 3 //FunctionCode
binary.BigEndian.PutUint16(req[8:10], first-1)
binary.BigEndian.PutUint16(req[10:12], numRegs)
m.conn.SetDeadline(time.Now().Add(5 * time.Second))
byteswritten, err := m.conn.Write(req)
if err != nil {
return nil, err
}
if byteswritten != requestLength {
return nil, fmt.Errorf("failed to send request")
}
m.conn.SetDeadline(time.Now().Add(5 * time.Second))
_, err = io.ReadFull(m.conn, m.header[:])
if err != nil {
return nil, err
}
responseHeader.unMarshal(m.header)
expectedDataLength := responseHeader.length - 1
response := make([]byte, expectedDataLength)
_, err = m.conn.Read(response)
if err != nil {
return nil, err
}
err = mbpayload.unMarshal(response)
if mbpayload.functionCode != 3 {
return nil, fmt.Errorf("modbus exception %v", mbpayload.functionCode&0x7F)
}
if err != nil {
return nil, err
}
m.t.Reset(m.keepAliveDuration)
return mbpayload.registers, nil
}
type mbapHeader struct {
transactionID uint16
protocolIdentifier uint16
length uint16
unitIdentifier byte
}
// func (m *mbapHeader) marshalBinary() ([]byte, error) {
// }
func (m *mbapHeader) unMarshal(data [7]byte) error {
m.transactionID = binary.BigEndian.Uint16(data[0:2])
m.protocolIdentifier = binary.BigEndian.Uint16(data[2:4])
m.length = binary.BigEndian.Uint16(data[4:6])
m.unitIdentifier = data[6]
return nil
}
type mbPDU struct {
functionCode uint8
length uint8
registers []uint16
}
func (d *mbPDU) unMarshal(data []byte) error {
d.functionCode = data[0]
d.length = data[1]
if d.length+2 != uint8(len(data)) {
return fmt.Errorf("lenght mismatch in modbus payload")
}
d.registers = make([]uint16, d.length/2)
var n uint8
for n < d.length/2 {
d.registers[n] = binary.BigEndian.Uint16(data[n*2+2 : n*2+4])
n++
}
return nil
}