7 Commits

Author SHA1 Message Date
f16b9dd009 Add support for reading input registers 2023-07-04 17:48:10 +02:00
8fbead4274 Don't reset transactionID when reopening TCP connection 2023-03-05 21:33:04 +01:00
8c33fd2a89 Set read deadline correctly 2023-02-25 22:30:31 +01:00
ebdbc92693 Remove a Printf 2023-02-25 15:05:39 +01:00
7b1533b3e6 Improve error messages, close conn on transactionID mismatch. 2023-02-25 13:04:25 +01:00
ce537e1373 Improve error handling 2023-02-25 11:08:53 +01:00
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
2 changed files with 130 additions and 33 deletions

View File

@@ -17,10 +17,12 @@ type Mbclient struct {
conn net.Conn
t *time.Timer
keepAliveDuration time.Duration
timeOut time.Duration
wg sync.WaitGroup
registerOffset uint16
}
func New(Address string, Unit uint8, KeepAlive time.Duration) (*Mbclient, error) {
func New(Address string, Unit uint8, KeepAlive, TimeOut time.Duration, registerOffset uint16) (*Mbclient, error) {
c := new(Mbclient)
c.address = Address
@@ -28,6 +30,8 @@ func New(Address string, Unit uint8, KeepAlive time.Duration) (*Mbclient, error)
c.t = time.NewTimer(0)
<-c.t.C
c.keepAliveDuration = KeepAlive
c.timeOut = TimeOut
c.registerOffset = registerOffset
return c, nil
}
@@ -35,17 +39,36 @@ func (m *Mbclient) closer() {
<-m.t.C
m.conn.Close()
//m.transactionCounter = 0
m.wg.Done()
}
func (m *Mbclient) ReadRegisters(first uint16, numRegs uint16) ([]uint16, error) {
// closeConn can be called when a tcp communication failure require the current
// connection to be closed, it should only be used when closer routine is running
// and the timer is stopped.
func (m *Mbclient) closeConn() {
m.t.Reset(0)
m.wg.Wait()
}
// Reads one or many holding registers (Function code 03)
func (m *Mbclient) ReadHoldingRegisters(first uint16, numRegs uint16) ([]uint16, error) {
return m.readRegisters(3, first, numRegs)
}
// Reads one or many input registers (Function code 04)
func (m *Mbclient) ReadInputRegisters(first uint16, numRegs uint16) ([]uint16, error) {
return m.readRegisters(4, first, numRegs)
}
func (m *Mbclient) readRegisters(functionCode uint8, 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.Dial("tcp", m.address)
m.conn, err = net.DialTimeout("tcp", m.address, m.timeOut)
if err != nil {
return nil, err
}
@@ -60,40 +83,58 @@ func (m *Mbclient) ReadRegisters(first uint16, numRegs uint16) ([]uint16, error)
var mbpayload mbPDU
binary.BigEndian.PutUint16(req[0:2], m.transactionCounter)
binary.BigEndian.PutUint16(req[2:4], 0)
binary.BigEndian.PutUint16(req[2:4], 0) // Protocol identifier
binary.BigEndian.PutUint16(req[4:6], 6) // Length
req[6] = m.unit
req[7] = 3 //FunctionCode
binary.BigEndian.PutUint16(req[8:10], first-1)
req[7] = functionCode
binary.BigEndian.PutUint16(req[8:10], first-m.registerOffset)
binary.BigEndian.PutUint16(req[10:12], numRegs)
m.conn.SetDeadline(time.Now().Add(10 * time.Second))
m.conn.SetDeadline(time.Now().Add(5 * time.Second))
byteswritten, err := m.conn.Write(req)
if err != nil {
m.closeConn()
return nil, err
}
if byteswritten != requestLength {
m.closeConn()
return nil, fmt.Errorf("failed to send request")
}
m.conn.SetDeadline(time.Now().Add(10 * time.Second))
m.conn.SetDeadline(time.Now().Add(m.timeOut))
_, err = io.ReadFull(m.conn, m.header[:])
if err != nil {
m.closeConn()
return nil, err
}
responseHeader.unMarshal(m.header)
expectedDataLength := responseHeader.length - 1
if m.transactionCounter != responseHeader.transactionID {
m.closeConn()
return nil, fmt.Errorf("modbus transaction mismatch %v != %v", m.transactionCounter, responseHeader.transactionID)
}
response := make([]byte, expectedDataLength)
_, err = m.conn.Read(response)
m.conn.SetDeadline(time.Now().Add(m.timeOut))
bytesRead, err := io.ReadFull(m.conn, response)
if err != nil {
m.closeConn()
return nil, err
}
if uint16(bytesRead) != expectedDataLength {
m.t.Reset(m.keepAliveDuration)
return nil, fmt.Errorf("failed to read complete package")
}
err = mbpayload.unMarshal(response)
if mbpayload.functionCode != 3 {
return nil, fmt.Errorf("modbus exception %v", mbpayload.functionCode&0x7F)
if mbpayload.functionCode != functionCode {
m.t.Reset(m.keepAliveDuration)
return nil, fmt.Errorf("modbus exception %v req: %x", mbpayload.functionCode&0x7F, req)
}
if err != nil {
m.t.Reset(m.keepAliveDuration)
return nil, err
}
m.t.Reset(m.keepAliveDuration)
return mbpayload.registers, nil
}
@@ -105,10 +146,6 @@ type mbapHeader struct {
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])
@@ -128,7 +165,7 @@ 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")
return fmt.Errorf("length mismatch in modbus payload")
}
d.registers = make([]uint16, d.length/2)
var n uint8

View File

@@ -7,58 +7,91 @@ import (
"github.com/stretchr/testify/assert"
)
const testNibeHost = "NIBE-06543922346009.solver.nu:502"
const testIAMhost = "IAM_248000012514.solver.nu:502"
func TestNibeInputRegsters(t *testing.T) {
inputRegs := []uint16{1, 5, 7 /*8,*/, 9, 10, 11, 12, 13, 14, 16 /*26,*/ /*39,*/, 86, 1046, 1083, 1100, 1102, 1104, // S1155 specific
40 /*46, 48, 50*/ /* 396, 398,*/, 1017 /*1567,*/, 1025, 1028, 1029, 1083, 1087, 1575, 1577 /*1581,*/, 1583, 1585, 1975} // common registers
c, err := New(testNibeHost, 1, 100*time.Millisecond, 5*time.Second, 0)
t.Log("Connect")
assert.NoError(t, err)
for _, reg := range inputRegs {
res, err := c.ReadInputRegisters(uint16(reg), 1)
assert.NoError(t, err, "Failed to read reg %v", reg)
if err == nil {
t.Logf("reg: %v res: %v \n", reg, res)
}
time.Sleep(10 * time.Millisecond)
}
time.Sleep(1 * time.Second)
}
func TestNibeHoldingRegsters(t *testing.T) {
inputRegs := []uint16{18, 20, 22, 26, 30, 34, 38, 39, 40, 41, 45, 43, 44, 45, 56 /*97, 159,*/, 196, 197, 237}
c, err := New(testNibeHost, 1, 100*time.Millisecond, 5*time.Second, 0)
t.Log("Connect")
assert.NoError(t, err)
for _, reg := range inputRegs {
res, err := c.ReadHoldingRegisters(uint16(reg), 1)
assert.NoError(t, err, "Failed to read reg %v", reg)
if err == nil {
t.Logf("reg: %v res: %v \n", reg, res)
}
time.Sleep(10 * time.Millisecond)
}
time.Sleep(1 * time.Second)
}
func TestReadOneRegisterKeepAlive(t *testing.T) {
c, err := New("IAM_248000012514.solver.nu:502", 1, 100*time.Millisecond)
c, err := New(testIAMhost, 1, 100*time.Millisecond, 5*time.Second, 1)
t.Log("Connect")
assert.NoError(t, err)
for n := 0; n < 5; n++ {
res, err := c.ReadRegisters(12401, 2)
res, err := c.ReadHoldingRegisters(12401, 2)
assert.NoError(t, err)
assert.Len(t, res, 2)
t.Log(res)
res, err = c.ReadRegisters(12102, 2)
res, err = c.ReadHoldingRegisters(12102, 2)
assert.NoError(t, err)
assert.Len(t, res, 2)
t.Log(res)
res, err = c.ReadRegisters(12544, 1)
assert.NoError(t, err)
assert.Len(t, res, 1)
t.Log(float32(res[0]) / 10)
res, err = c.ReadRegisters(12136, 1)
assert.NoError(t, err)
assert.Len(t, res, 1)
t.Log(res)
}
time.Sleep(1 * time.Second)
}
func TestReadOneRegisterShortKeepAlive(t *testing.T) {
c, err := New("IAM_248000012514.solver.nu:502", 1, 10*time.Nanosecond)
c, err := New(testIAMhost, 1, 10*time.Nanosecond, 5*time.Second, 1)
t.Log("Connect")
assert.NoError(t, err)
for n := 0; n < 5; n++ {
res, err := c.ReadRegisters(12401, 2)
res, err := c.ReadHoldingRegisters(12401, 2)
assert.NoError(t, err)
assert.Len(t, res, 2)
t.Log(res)
time.Sleep(100 * time.Millisecond)
res, err = c.ReadRegisters(12102, 2)
res, err = c.ReadHoldingRegisters(12102, 2)
assert.NoError(t, err)
assert.Len(t, res, 2)
t.Log(res)
time.Sleep(100 * time.Millisecond)
res, err = c.ReadRegisters(12544, 1)
res, err = c.ReadHoldingRegisters(12544, 1)
assert.NoError(t, err)
assert.Len(t, res, 1)
t.Log(float32(res[0]) / 10)
time.Sleep(100 * time.Millisecond)
res, err = c.ReadRegisters(12136, 1)
res, err = c.ReadHoldingRegisters(12136, 1)
assert.NoError(t, err)
assert.Len(t, res, 1)
t.Log(res)
@@ -66,3 +99,30 @@ func TestReadOneRegisterShortKeepAlive(t *testing.T) {
}
time.Sleep(1 * time.Second)
}
func TestReadALot(t *testing.T) {
c, err := New(testIAMhost, 1, 100*time.Millisecond, 5*time.Second, 1)
t.Log("Connect")
assert.NoError(t, err)
for n := 0; n < 500; n++ {
t.Log(n)
_, err := c.ReadHoldingRegisters(12401, 2)
if err != nil {
t.Log(err)
}
_, err = c.ReadHoldingRegisters(12102, 2)
if err != nil {
t.Log(err)
}
_, err = c.ReadHoldingRegisters(12544, 1)
if err != nil {
t.Log(err)
}
_, err = c.ReadHoldingRegisters(12136, 1)
if err != nil {
t.Log(err)
}
}
time.Sleep(200 * time.Millisecond)
}