forked from grid-x/modbus
-
Notifications
You must be signed in to change notification settings - Fork 1
/
rtu_over_udp_client.go
173 lines (147 loc) · 4.28 KB
/
rtu_over_udp_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package modbus
import (
"fmt"
"io"
"net"
"sync"
)
// ErrADURequestLength informs about a wrong ADU request length.
type ErrADURequestLength int
func (length ErrADURequestLength) Error() string {
return fmt.Sprintf("modbus: ADU request length '%d' must not be less than 2", length)
}
// ErrADUResponseLength informs about a wrong ADU request length.
type ErrADUResponseLength int
func (length ErrADUResponseLength) Error() string {
return fmt.Sprintf("modbus: ADU response length '%d' must not be less than 2", length)
}
// RTUOverUDPClientHandler implements Packager and Transporter interface.
type RTUOverUDPClientHandler struct {
rtuPackager
*rtuUDPTransporter
}
// NewRTUOverUDPClientHandler allocates and initializes a RTUOverUDPClientHandler.
func NewRTUOverUDPClientHandler(address string) *RTUOverUDPClientHandler {
return &RTUOverUDPClientHandler{
rtuUDPTransporter: &rtuUDPTransporter{
Address: address,
},
}
}
// RTUOverUDPClient creates RTU over UDP client with default handler and given connect string.
func RTUOverUDPClient(address string) Client {
handler := NewRTUOverUDPClientHandler(address)
return NewClient(handler)
}
// Clone creates a new client handler with the same underlying shared transport.
func (mb *RTUOverUDPClientHandler) Clone() *RTUOverUDPClientHandler {
return &RTUOverUDPClientHandler{
rtuUDPTransporter: mb.rtuUDPTransporter,
}
}
// rtuUDPTransporter implements Transporter interface.
type rtuUDPTransporter struct {
// Connect string
Address string
// Transmission logger
Logger Logger
// UDP connection
mu sync.Mutex
conn net.Conn
}
// Send sends data to server and ensures adequate response for request type
func (mb *rtuUDPTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
mb.mu.Lock()
defer mb.mu.Unlock()
// Check ADU request length
if len(aduRequest) < 2 {
err = ErrADURequestLength(len(aduRequest))
return
}
// Establish a new connection if not connected
if err = mb.connect(); err != nil {
return
}
// Send the request
mb.logf("modbus: send % x\n", aduRequest)
if _, err = mb.conn.Write(aduRequest); err != nil {
return
}
function := aduRequest[1]
functionFail := aduRequest[1] & 0x80
bytesToRead := calculateResponseLength(aduRequest)
var n, n1 int
var data [rtuMaxSize]byte
// We first read the minimum length and then read either the full package
// or the error package, depending on the error status (byte 2 of the response)
n, err = io.ReadAtLeast(mb.conn, data[:], rtuMinSize)
if err != nil {
return
}
// Check ADU response length
if len(data) < 2 {
err = ErrADUResponseLength(len(data))
return
}
// if the function is correct
if data[1] == function {
// we read the rest of the bytes
if n < bytesToRead {
if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize {
n1, err = io.ReadFull(mb.conn, data[n:bytesToRead])
n += n1
}
}
} else if data[1] == functionFail {
// for error we need to read 5 bytes
if n < rtuExceptionSize {
n1, err = io.ReadFull(mb.conn, data[n:rtuExceptionSize])
}
n += n1
}
if err != nil {
return
}
aduResponse = data[:n]
mb.logf("modbus: recv % x\n", aduResponse)
return
}
func (mb *rtuUDPTransporter) logf(format string, v ...interface{}) {
if mb.Logger != nil {
mb.Logger.Printf(format, v...)
}
}
// Connect establishes a new connection to the address in Address.
func (mb *rtuUDPTransporter) Connect() error {
mb.mu.Lock()
defer mb.mu.Unlock()
return mb.connect()
}
// connect establishes a new connection to the address in Address. Caller must hold the mutex before calling this method.
// Since UDP is connectionless this does little more than setting up the connection object.
func (mb *rtuUDPTransporter) connect() error {
if mb.conn == nil {
dialer := net.Dialer{}
conn, err := dialer.Dial("udp", mb.Address)
if err != nil {
return err
}
mb.conn = conn
}
return nil
}
// Close closes current connection.
func (mb *rtuUDPTransporter) Close() error {
mb.mu.Lock()
defer mb.mu.Unlock()
return mb.close()
}
// close closes current connection. Caller must hold the mutex before calling this method.
// Since UDP is connectionless this does little more than freeing up the connection object.
func (mb *rtuUDPTransporter) close() (err error) {
if mb.conn != nil {
err = mb.conn.Close()
mb.conn = nil
}
return
}