From e489071eba29f7a1c7821318e6a4e5982e43d174 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 2 May 2023 12:11:16 +0200 Subject: [PATCH 1/6] Add handler.Clone for sharing underlying transport between packagers --- ascii_over_tcp_client.go | 27 ++++++++++++++++++--------- asciiclient.go | 27 ++++++++++++++++++--------- rtu_over_tcp_client.go | 28 +++++++++++++++++++--------- rtuclient.go | 28 +++++++++++++++++++--------- serial.go | 11 +++++++++++ tcpclient.go | 35 ++++++++++++++++++++++++++--------- 6 files changed, 111 insertions(+), 45 deletions(-) diff --git a/ascii_over_tcp_client.go b/ascii_over_tcp_client.go index cc263e4..5d7d61f 100644 --- a/ascii_over_tcp_client.go +++ b/ascii_over_tcp_client.go @@ -8,25 +8,34 @@ import ( "time" ) +// ASCIIOverTCPClient creates ASCII over TCP client with default handler and given connect string. +func ASCIIOverTCPClient(address string) Client { + handler := NewASCIIOverTCPClientHandler(address) + return NewClient(handler) +} + // ASCIIOverTCPClientHandler implements Packager and Transporter interface. type ASCIIOverTCPClientHandler struct { asciiPackager - asciiTCPTransporter + *asciiTCPTransporter } // NewASCIIOverTCPClientHandler allocates and initializes a ASCIIOverTCPClientHandler. func NewASCIIOverTCPClientHandler(address string) *ASCIIOverTCPClientHandler { - handler := &ASCIIOverTCPClientHandler{} - handler.Address = address - handler.Timeout = tcpTimeout - handler.IdleTimeout = tcpIdleTimeout + handler := &ASCIIOverTCPClientHandler{ + asciiTCPTransporter: &asciiTCPTransporter{ + defaultTCPTransporter(address), + }, + } return handler } -// ASCIIOverTCPClient creates ASCII over TCP client with default handler and given connect string. -func ASCIIOverTCPClient(address string) Client { - handler := NewASCIIOverTCPClientHandler(address) - return NewClient(handler) +// Clone creates a new client handler with the same underlying shared transport. +func (mb *ASCIIOverTCPClientHandler) Clone() *ASCIIOverTCPClientHandler { + h := &ASCIIOverTCPClientHandler{ + asciiTCPTransporter: mb.asciiTCPTransporter, + } + return h } // asciiTCPTransporter implements Transporter interface. diff --git a/asciiclient.go b/asciiclient.go index aadfd32..8e3e25a 100644 --- a/asciiclient.go +++ b/asciiclient.go @@ -22,26 +22,35 @@ const ( // Modbus ASCII defines ':' but in the field often '>' is seen. var asciiStart = []string{":", ">"} +// ASCIIClient creates ASCII client with default handler and given connect string. +func ASCIIClient(address string) Client { + handler := NewASCIIClientHandler(address) + return NewClient(handler) +} + // ASCIIClientHandler implements Packager and Transporter interface. type ASCIIClientHandler struct { asciiPackager - asciiSerialTransporter + *asciiSerialTransporter } // NewASCIIClientHandler allocates and initializes a ASCIIClientHandler. func NewASCIIClientHandler(address string) *ASCIIClientHandler { - handler := &ASCIIClientHandler{} - handler.Address = address - handler.Timeout = serialTimeout - handler.IdleTimeout = serialIdleTimeout + handler := &ASCIIClientHandler{ + asciiSerialTransporter: &asciiSerialTransporter{ + serialPort: defaultSerialPort(address), + }, + } handler.serialPort.Logger = handler // expose the logger return handler } -// ASCIIClient creates ASCII client with default handler and given connect string. -func ASCIIClient(address string) Client { - handler := NewASCIIClientHandler(address) - return NewClient(handler) +// Clone creates a new client handler with the same underlying shared transport. +func (mb *ASCIIClientHandler) Clone() *ASCIIClientHandler { + h := &ASCIIClientHandler{ + asciiSerialTransporter: mb.asciiSerialTransporter, + } + return h } // asciiPackager implements Packager interface. diff --git a/rtu_over_tcp_client.go b/rtu_over_tcp_client.go index d9ec19d..f29cf00 100644 --- a/rtu_over_tcp_client.go +++ b/rtu_over_tcp_client.go @@ -9,25 +9,35 @@ import ( "time" ) +// RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. +func RTUOverTCPClient(address string) Client { + handler := NewRTUOverTCPClientHandler(address) + return NewClient(handler) +} + // RTUOverTCPClientHandler implements Packager and Transporter interface. type RTUOverTCPClientHandler struct { rtuPackager - rtuTCPTransporter + *rtuTCPTransporter } // NewRTUOverTCPClientHandler allocates and initializes a RTUOverTCPClientHandler. func NewRTUOverTCPClientHandler(address string) *RTUOverTCPClientHandler { - handler := &RTUOverTCPClientHandler{} - handler.Address = address - handler.Timeout = tcpTimeout - handler.IdleTimeout = tcpIdleTimeout + handler := &RTUOverTCPClientHandler{ + rtuTCPTransporter: &rtuTCPTransporter{ + defaultTCPTransporter(address), + }, + } + return handler } -// RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. -func RTUOverTCPClient(address string) Client { - handler := NewRTUOverTCPClientHandler(address) - return NewClient(handler) +// Clone creates a new client handler with the same underlying shared transport. +func (mb *RTUOverTCPClientHandler) Clone() *RTUOverTCPClientHandler { + h := &RTUOverTCPClientHandler{ + rtuTCPTransporter: mb.rtuTCPTransporter, + } + return h } // rtuTCPTransporter implements Transporter interface. diff --git a/rtuclient.go b/rtuclient.go index be38d44..8ac78e4 100644 --- a/rtuclient.go +++ b/rtuclient.go @@ -42,26 +42,36 @@ const ( readFifoQueueFunctionCode = 0x18 ) +// RTUClient creates RTU client with default handler and given connect string. +func RTUClient(address string) Client { + handler := NewRTUClientHandler(address) + return NewClient(handler) +} + // RTUClientHandler implements Packager and Transporter interface. type RTUClientHandler struct { rtuPackager - rtuSerialTransporter + *rtuSerialTransporter } // NewRTUClientHandler allocates and initializes a RTUClientHandler. func NewRTUClientHandler(address string) *RTUClientHandler { - handler := &RTUClientHandler{} - handler.Address = address - handler.Timeout = serialTimeout - handler.IdleTimeout = serialIdleTimeout + handler := &RTUClientHandler{ + rtuSerialTransporter: &rtuSerialTransporter{ + serialPort: defaultSerialPort(address), + }, + } + handler.serialPort.Logger = handler // expose the logger return handler } -// RTUClient creates RTU client with default handler and given connect string. -func RTUClient(address string) Client { - handler := NewRTUClientHandler(address) - return NewClient(handler) +// Clone creates a new client handler with the same underlying shared transport. +func (mb *RTUClientHandler) Clone() *RTUClientHandler { + h := &RTUClientHandler{ + rtuSerialTransporter: mb.rtuSerialTransporter, + } + return h } // rtuPackager implements Packager interface. diff --git a/serial.go b/serial.go index a910b95..18ca13e 100644 --- a/serial.go +++ b/serial.go @@ -34,6 +34,17 @@ type serialPort struct { closeTimer *time.Timer } +// defaultSerialPort creates a serialPort with default configuration. +func defaultSerialPort(address string) serialPort { + return serialPort{ + Config: serial.Config{ + Address: address, + Timeout: serialTimeout, + }, + IdleTimeout: serialIdleTimeout, + } +} + func (mb *serialPort) Connect() (err error) { mb.mu.Lock() defer mb.mu.Unlock() diff --git a/tcpclient.go b/tcpclient.go index 57b3ffd..1447043 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -33,25 +33,33 @@ func (length ErrTCPHeaderLength) Error() string { length, tcpMaxLength-tcpHeaderSize+1) } +// TCPClient creates TCP client with default handler and given connect string. +func TCPClient(address string) Client { + handler := NewTCPClientHandler(address) + return NewClient(handler) +} + // TCPClientHandler implements Packager and Transporter interface. type TCPClientHandler struct { tcpPackager - tcpTransporter + *tcpTransporter } // NewTCPClientHandler allocates a new TCPClientHandler. func NewTCPClientHandler(address string) *TCPClientHandler { - h := &TCPClientHandler{} - h.Address = address - h.Timeout = tcpTimeout - h.IdleTimeout = tcpIdleTimeout + transport := defaultTCPTransporter(address) + h := &TCPClientHandler{ + tcpTransporter: &transport, + } return h } -// TCPClient creates TCP client with default handler and given connect string. -func TCPClient(address string) Client { - handler := NewTCPClientHandler(address) - return NewClient(handler) +// Clone creates a new client handler with the same underlying shared transport. +func (mb *TCPClientHandler) Clone() *TCPClientHandler { + h := &TCPClientHandler{ + tcpTransporter: mb.tcpTransporter, + } + return h } // tcpPackager implements Packager interface. @@ -148,6 +156,15 @@ type tcpTransporter struct { lastSuccessfulTransactionID uint16 } +// defaultTCPTransporter creates a new tcpTransporter with default values. +func defaultTCPTransporter(address string) tcpTransporter { + return tcpTransporter{ + Address: address, + Timeout: tcpTimeout, + IdleTimeout: tcpIdleTimeout, + } +} + // helper value to signify what to do in Send type readResult int From c1122c7a9a1134a8b1961df1c168a14121ce3013 Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 11 Sep 2024 20:01:50 +0200 Subject: [PATCH 2/6] TCPClient: share transaction id between packager and transporter --- tcpclient.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tcpclient.go b/tcpclient.go index 1447043..3bfca76 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -48,24 +48,24 @@ type TCPClientHandler struct { // NewTCPClientHandler allocates a new TCPClientHandler. func NewTCPClientHandler(address string) *TCPClientHandler { transport := defaultTCPTransporter(address) - h := &TCPClientHandler{ + return &TCPClientHandler{ + tcpPackager: tcpPackager{transactionID: &transport.transactionID}, tcpTransporter: &transport, } - return h } // Clone creates a new client handler with the same underlying shared transport. func (mb *TCPClientHandler) Clone() *TCPClientHandler { - h := &TCPClientHandler{ + return &TCPClientHandler{ + tcpPackager: tcpPackager{transactionID: &mb.tcpTransporter.transactionID}, tcpTransporter: mb.tcpTransporter, } - return h } // tcpPackager implements Packager interface. type tcpPackager struct { // For synchronization between messages of server & client - transactionID uint32 + transactionID *uint32 // Broadcast address is 0 SlaveID byte } @@ -87,7 +87,7 @@ func (mb *tcpPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { adu = make([]byte, tcpHeaderSize+1+len(pdu.Data)) // Transaction identifier - transactionID := atomic.AddUint32(&mb.transactionID, 1) + transactionID := atomic.AddUint32(mb.transactionID, 1) binary.BigEndian.PutUint16(adu, uint16(transactionID)) // Protocol identifier binary.BigEndian.PutUint16(adu[2:], tcpProtocolIdentifier) @@ -154,6 +154,9 @@ type tcpTransporter struct { lastAttemptedTransactionID uint16 lastSuccessfulTransactionID uint16 + + // For synchronization between messages of server & client + transactionID uint32 } // defaultTCPTransporter creates a new tcpTransporter with default values. From bb6305a0b24cd32f7896c1682feaaf75ad021598 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 26 Oct 2024 16:16:29 +0200 Subject: [PATCH 3/6] Fix tests (#10) --- tcpclient_prop_test.go | 5 +++-- tcpclient_test.go | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tcpclient_prop_test.go b/tcpclient_prop_test.go index 87f18e2..00043a1 100644 --- a/tcpclient_prop_test.go +++ b/tcpclient_prop_test.go @@ -9,9 +9,10 @@ import ( func TestTCPEncodeDecode(t *testing.T) { rapid.Check(t, func(t *rapid.T) { + transactionID := rapid.Uint32().Draw(t, "transactionID") packager := &tcpPackager{ - transactionID: rapid.Uint32().Draw(t, "transactionID").(uint32), - SlaveID: rapid.Byte().Draw(t, "SlaveID").(byte), + transactionID: &transactionID, + SlaveID: rapid.Byte().Draw(t, "SlaveID"), } pdu := &ProtocolDataUnit{ diff --git a/tcpclient_test.go b/tcpclient_test.go index 0871083..89d1053 100644 --- a/tcpclient_test.go +++ b/tcpclient_test.go @@ -30,8 +30,9 @@ func TestTCPEncoding(t *testing.T) { } func TestTCPDecoding(t *testing.T) { + var transactionID uint32 = 1 packager := tcpPackager{} - packager.transactionID = 1 + packager.transactionID = &transactionID packager.SlaveID = 17 adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3} From 92b6bf7266d9a5fbd52260ae01f8a16b3c8936e1 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 28 Oct 2024 18:28:22 +0100 Subject: [PATCH 4/6] wip --- ascii_over_tcp_client.go | 6 ++---- asciiclient.go | 3 +-- rtu_over_tcp_client.go | 17 +++++++---------- rtuclient.go | 3 +-- tcpclient.go | 12 ++++++------ 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/ascii_over_tcp_client.go b/ascii_over_tcp_client.go index eeab15c..a146dc0 100644 --- a/ascii_over_tcp_client.go +++ b/ascii_over_tcp_client.go @@ -22,20 +22,18 @@ type ASCIIOverTCPClientHandler struct { // NewASCIIOverTCPClientHandler allocates and initializes a ASCIIOverTCPClientHandler. func NewASCIIOverTCPClientHandler(address string) *ASCIIOverTCPClientHandler { - handler := &ASCIIOverTCPClientHandler{ + return &ASCIIOverTCPClientHandler{ asciiTCPTransporter: &asciiTCPTransporter{ defaultTCPTransporter(address), }, } - return handler } // Clone creates a new client handler with the same underlying shared transport. func (mb *ASCIIOverTCPClientHandler) Clone() *ASCIIOverTCPClientHandler { - h := &ASCIIOverTCPClientHandler{ + return &ASCIIOverTCPClientHandler{ asciiTCPTransporter: mb.asciiTCPTransporter, } - return h } // asciiTCPTransporter implements Transporter interface. diff --git a/asciiclient.go b/asciiclient.go index a3e15f7..0f3d7a0 100644 --- a/asciiclient.go +++ b/asciiclient.go @@ -45,10 +45,9 @@ func ASCIIClient(address string) Client { // Clone creates a new client handler with the same underlying shared transport. func (mb *ASCIIClientHandler) Clone() *ASCIIClientHandler { - h := &ASCIIClientHandler{ + return &ASCIIClientHandler{ asciiSerialTransporter: mb.asciiSerialTransporter, } - return h } // asciiPackager implements Packager interface. diff --git a/rtu_over_tcp_client.go b/rtu_over_tcp_client.go index 5194a62..2c6c6bc 100644 --- a/rtu_over_tcp_client.go +++ b/rtu_over_tcp_client.go @@ -9,12 +9,6 @@ import ( "time" ) -// RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. -func RTUOverTCPClient(address string) Client { - handler := NewRTUOverTCPClientHandler(address) - return NewClient(handler) -} - // RTUOverTCPClientHandler implements Packager and Transporter interface. type RTUOverTCPClientHandler struct { rtuPackager @@ -23,21 +17,24 @@ type RTUOverTCPClientHandler struct { // NewRTUOverTCPClientHandler allocates and initializes a RTUOverTCPClientHandler. func NewRTUOverTCPClientHandler(address string) *RTUOverTCPClientHandler { - handler := &RTUOverTCPClientHandler{ + return &RTUOverTCPClientHandler{ rtuTCPTransporter: &rtuTCPTransporter{ defaultTCPTransporter(address), }, } +} - return handler +// RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. +func RTUOverTCPClient(address string) Client { + handler := NewRTUOverTCPClientHandler(address) + return NewClient(handler) } // Clone creates a new client handler with the same underlying shared transport. func (mb *RTUOverTCPClientHandler) Clone() *RTUOverTCPClientHandler { - h := &RTUOverTCPClientHandler{ + return &RTUOverTCPClientHandler{ rtuTCPTransporter: mb.rtuTCPTransporter, } - return h } // rtuTCPTransporter implements Transporter interface. diff --git a/rtuclient.go b/rtuclient.go index 1208e75..4997955 100644 --- a/rtuclient.go +++ b/rtuclient.go @@ -66,10 +66,9 @@ func RTUClient(address string) Client { // Clone creates a new client handler with the same underlying shared transport. func (mb *RTUClientHandler) Clone() *RTUClientHandler { - h := &RTUClientHandler{ + return &RTUClientHandler{ rtuSerialTransporter: mb.rtuSerialTransporter, } - return h } // rtuPackager implements Packager interface. diff --git a/tcpclient.go b/tcpclient.go index b4b2b31..9cae682 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -33,12 +33,6 @@ func (length ErrTCPHeaderLength) Error() string { length, tcpMaxLength-tcpHeaderSize+1) } -// TCPClient creates TCP client with default handler and given connect string. -func TCPClient(address string) Client { - handler := NewTCPClientHandler(address) - return NewClient(handler) -} - // TCPClientHandler implements Packager and Transporter interface. type TCPClientHandler struct { tcpPackager @@ -54,6 +48,12 @@ func NewTCPClientHandler(address string) *TCPClientHandler { } } +// TCPClient creates TCP client with default handler and given connect string. +func TCPClient(address string) Client { + handler := NewTCPClientHandler(address) + return NewClient(handler) +} + // Clone creates a new client handler with the same underlying shared transport. func (mb *TCPClientHandler) Clone() *TCPClientHandler { return &TCPClientHandler{ From 406db239193125d23c7e41e2e4ab1d8d6b35b3b3 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 28 Oct 2024 18:29:20 +0100 Subject: [PATCH 5/6] wip --- ascii_over_tcp_client.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ascii_over_tcp_client.go b/ascii_over_tcp_client.go index a146dc0..e0722be 100644 --- a/ascii_over_tcp_client.go +++ b/ascii_over_tcp_client.go @@ -8,12 +8,6 @@ import ( "time" ) -// ASCIIOverTCPClient creates ASCII over TCP client with default handler and given connect string. -func ASCIIOverTCPClient(address string) Client { - handler := NewASCIIOverTCPClientHandler(address) - return NewClient(handler) -} - // ASCIIOverTCPClientHandler implements Packager and Transporter interface. type ASCIIOverTCPClientHandler struct { asciiPackager @@ -29,6 +23,12 @@ func NewASCIIOverTCPClientHandler(address string) *ASCIIOverTCPClientHandler { } } +// ASCIIOverTCPClient creates ASCII over TCP client with default handler and given connect string. +func ASCIIOverTCPClient(address string) Client { + handler := NewASCIIOverTCPClientHandler(address) + return NewClient(handler) +} + // Clone creates a new client handler with the same underlying shared transport. func (mb *ASCIIOverTCPClientHandler) Clone() *ASCIIOverTCPClientHandler { return &ASCIIOverTCPClientHandler{ From 1b97a56e0262b0e2157ed8ef212d0edb235f92d3 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 6 Jan 2025 17:42:04 +0100 Subject: [PATCH 6/6] Update tcpclient_test.go --- tcpclient_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tcpclient_test.go b/tcpclient_test.go index 0fdef7d..8a9c899 100644 --- a/tcpclient_test.go +++ b/tcpclient_test.go @@ -13,7 +13,8 @@ import ( ) func TestTCPEncoding(t *testing.T) { - packager := tcpPackager{} + var transactionID uint32 = 1 + packager := tcpPackager{transactionID: &transactionID} pdu := ProtocolDataUnit{} pdu.FunctionCode = 3 pdu.Data = []byte{0, 4, 0, 3} @@ -31,8 +32,7 @@ func TestTCPEncoding(t *testing.T) { func TestTCPDecoding(t *testing.T) { var transactionID uint32 = 1 - packager := tcpPackager{} - packager.transactionID = &transactionID + packager := tcpPackager{transactionID: &transactionID} packager.SlaveID = 17 adu := []byte{0, 1, 0, 0, 0, 6, 17, 3, 0, 120, 0, 3}