@@ -84,26 +84,7 @@ func (mb *tcpPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) {
84
84
85
85
// Verify confirms transaction, protocol and unit id.
86
86
func (mb * tcpPackager ) Verify (aduRequest []byte , aduResponse []byte ) (err error ) {
87
- // Transaction id
88
- responseVal := binary .BigEndian .Uint16 (aduResponse )
89
- requestVal := binary .BigEndian .Uint16 (aduRequest )
90
- if responseVal != requestVal {
91
- err = fmt .Errorf ("modbus: response transaction id '%v' does not match request '%v'" , responseVal , requestVal )
92
- return
93
- }
94
- // Protocol id
95
- responseVal = binary .BigEndian .Uint16 (aduResponse [2 :])
96
- requestVal = binary .BigEndian .Uint16 (aduRequest [2 :])
97
- if responseVal != requestVal {
98
- err = fmt .Errorf ("modbus: response protocol id '%v' does not match request '%v'" , responseVal , requestVal )
99
- return
100
- }
101
- // Unit id (1 byte)
102
- if aduResponse [6 ] != aduRequest [6 ] {
103
- err = fmt .Errorf ("modbus: response unit id '%v' does not match request '%v'" , aduResponse [6 ], aduRequest [6 ])
104
- return
105
- }
106
- return
87
+ return verify (aduRequest , aduResponse )
107
88
}
108
89
109
90
// Decode extracts PDU from TCP frame:
@@ -134,6 +115,10 @@ type tcpTransporter struct {
134
115
Timeout time.Duration
135
116
// Idle timeout to close the connection
136
117
IdleTimeout time.Duration
118
+ // Recovery timeout if tcp communication misbehaves
119
+ LinkRecoveryTimeout time.Duration
120
+ // Recovery timeout if the protocol is malformed, e.g. wrong transaction ID
121
+ ProtocolRecoveryTimeout time.Duration
137
122
// Transmission logger
138
123
Logger * log.Logger
139
124
@@ -149,31 +134,58 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error
149
134
mb .mu .Lock ()
150
135
defer mb .mu .Unlock ()
151
136
137
+ var data [tcpMaxLength ]byte
138
+ recoveryDeadline := time .Now ().Add (mb .IdleTimeout )
139
+
152
140
// Establish a new connection if not connected
153
141
if err = mb .connect (); err != nil {
154
142
return
155
143
}
156
- // Set timer to close when idle
157
- mb .lastActivity = time .Now ()
158
- mb .startCloseTimer ()
159
- // Set write and read timeout
160
- var timeout time.Time
161
- if mb .Timeout > 0 {
162
- timeout = mb .lastActivity .Add (mb .Timeout )
163
- }
164
- if err = mb .conn .SetDeadline (timeout ); err != nil {
165
- return
166
- }
167
- // Send data
168
- mb .logf ("modbus: sending % x" , aduRequest )
169
- if _ , err = mb .conn .Write (aduRequest ); err != nil {
170
- return
171
- }
172
- // Read header first
173
- var data [tcpMaxLength ]byte
174
- if _ , err = io .ReadFull (mb .conn , data [:tcpHeaderSize ]); err != nil {
175
- return
144
+
145
+ for {
146
+ // Set timer to close when idle
147
+ mb .lastActivity = time .Now ()
148
+ mb .startCloseTimer ()
149
+ // Set write and read timeout
150
+ var timeout time.Time
151
+ if mb .Timeout > 0 {
152
+ timeout = mb .lastActivity .Add (mb .Timeout )
153
+ }
154
+ if err = mb .conn .SetDeadline (timeout ); err != nil {
155
+ return
156
+ }
157
+ // Send data
158
+ mb .logf ("modbus: sending % x" , aduRequest )
159
+ if _ , err = mb .conn .Write (aduRequest ); err != nil {
160
+ return
161
+ }
162
+ // Read header first
163
+ if _ , err = io .ReadFull (mb .conn , data [:tcpHeaderSize ]); err == nil {
164
+ aduResponse , err = mb .processResponse (data [:])
165
+ if err == nil && mb .ProtocolRecoveryTimeout > 0 && recoveryDeadline .Sub (time .Now ()) > 0 &&
166
+ verify (aduRequest , aduResponse ) != nil {
167
+ continue
168
+ }
169
+ mb .logf ("modbus: received % x\n " , aduResponse )
170
+ return
171
+ // Read attempt failed
172
+ } else if (err != io .EOF && err != io .ErrUnexpectedEOF ) ||
173
+ mb .LinkRecoveryTimeout == 0 || recoveryDeadline .Sub (time .Now ()) < 0 {
174
+ return
175
+ }
176
+ mb .logf ("modbus: close connection and retry, because of %v" , err )
177
+
178
+ mb .close ()
179
+ time .Sleep (mb .LinkRecoveryTimeout )
180
+
181
+ // Establish a new connection if not connected
182
+ if err = mb .connect (); err != nil {
183
+ return
184
+ }
176
185
}
186
+ }
187
+
188
+ func (mb * tcpTransporter ) processResponse (data []byte ) (aduResponse []byte , err error ) {
177
189
// Read length, ignore transaction & protocol id (4 bytes)
178
190
length := int (binary .BigEndian .Uint16 (data [4 :]))
179
191
if length <= 0 {
@@ -196,6 +208,29 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error
196
208
return
197
209
}
198
210
211
+ func verify (aduRequest []byte , aduResponse []byte ) (err error ) {
212
+ // Transaction id
213
+ responseVal := binary .BigEndian .Uint16 (aduResponse )
214
+ requestVal := binary .BigEndian .Uint16 (aduRequest )
215
+ if responseVal != requestVal {
216
+ err = fmt .Errorf ("modbus: response transaction id '%v' does not match request '%v'" , responseVal , requestVal )
217
+ return
218
+ }
219
+ // Protocol id
220
+ responseVal = binary .BigEndian .Uint16 (aduResponse [2 :])
221
+ requestVal = binary .BigEndian .Uint16 (aduRequest [2 :])
222
+ if responseVal != requestVal {
223
+ err = fmt .Errorf ("modbus: response protocol id '%v' does not match request '%v'" , responseVal , requestVal )
224
+ return
225
+ }
226
+ // Unit id (1 byte)
227
+ if aduResponse [6 ] != aduRequest [6 ] {
228
+ err = fmt .Errorf ("modbus: response unit id '%v' does not match request '%v'" , aduResponse [6 ], aduRequest [6 ])
229
+ return
230
+ }
231
+ return
232
+ }
233
+
199
234
// Connect establishes a new connection to the address in Address.
200
235
// Connect and Close are exported so that multiple requests can be done with one session
201
236
func (mb * tcpTransporter ) Connect () error {
0 commit comments