-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathQuery.go
374 lines (343 loc) · 10.4 KB
/
Query.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
package modbus
import (
"encoding/binary"
"fmt"
)
// Query contains the necessary data for a Packager to construct and execute a
// Modbus query.
type Query struct {
FunctionCode
SlaveID byte
Address uint16
Quantity uint16
Values []uint16
}
// IsValid returns a bool representing whether the Query is well constructed
// with a supported FunctionCode and appropriate Quantity and len(Values). If
// the query is invalid, IsValid returns false, and an error describing the
// reason for not passing. Otherwise it returns true, nil.
func (q Query) IsValid() (bool, error) {
errString, _ := FunctionNames[q.FunctionCode]
var maxQuantity uint16
var expectedLen int
switch q.FunctionCode {
case FunctionReadCoils:
fallthrough
case FunctionReadDiscreteInputs:
maxQuantity = 2000
case FunctionReadHoldingRegisters:
fallthrough
case FunctionReadInputRegisters:
maxQuantity = 125
case FunctionWriteSingleCoil:
expectedLen = 1
case FunctionWriteSingleRegister:
expectedLen = 1
case FunctionWriteMultipleCoils:
maxQuantity = 2000
expectedLen = int(q.Quantity) / 16
if q.Quantity%16 != 0 {
expectedLen++
}
case FunctionWriteMultipleRegisters:
maxQuantity = 123
expectedLen = int(q.Quantity)
case FunctionMaskWriteRegister:
expectedLen = 2
default:
return false, fmt.Errorf("Invalid FunctionCode: %x", q.FunctionCode)
}
// Check quantity
if (isReadFunction(q.FunctionCode) || isWriteMultipleFunction(q.FunctionCode)) &&
(q.Quantity == 0 || q.Address+q.Quantity > maxQuantity) {
return false, fmt.Errorf("%v: Requested address range [%v, %v] out of range [0, %v]",
errString, q.Address, q.Address+q.Quantity, maxQuantity)
}
// Check len(Values)
if isWriteFunction(q.FunctionCode) {
if len(q.Values) != expectedLen {
return false, fmt.Errorf(
"%v: len(Values) should be %v but it is: %v",
errString, expectedLen, len(q.Values))
}
}
return true, nil
}
// isValidResponse is used by Packagers to validate the response data against a
// given Query.
func (q Query) isValidResponse(response []byte) (bool, error) {
if nil == response || len(response) == 0 {
return false, exceptions[exceptionEmptyResponse]
}
if response[0] != q.SlaveID {
return false, exceptions[exceptionSlaveIDMismatch]
}
// Check for Modbus Exception Response
if FunctionCode(response[1]) != q.FunctionCode {
if FunctionCode(response[1]&0x7f) == q.FunctionCode {
switch response[2] {
case exceptionIllegalFunction:
fallthrough
case exceptionDataAddress:
fallthrough
case exceptionDataValue:
fallthrough
case exceptionSlaveDeviceFailure:
fallthrough
case exceptionAcknowledge:
fallthrough
case exceptionSlaveDeviceBusy:
fallthrough
case exceptionMemoryParityError:
fallthrough
case exceptionGatewayPathUnavailable:
fallthrough
case exceptionGatewayTargetDeviceFailedToRespond:
return false, exceptions[uint16(response[2])]
default:
return false, exceptions[exceptionUnknown]
}
}
return false, exceptions[exceptionUnknown]
}
if isWriteFunction(q.FunctionCode) {
data, _ := q.data()
for i := 0; i < 4; i++ {
if i >= len(response) || data[i] != response[2+i] {
return false, exceptions[exceptionWriteDataMismatch]
}
}
}
if isReadFunction(q.FunctionCode) {
var expectedLen int
switch q.FunctionCode {
case FunctionReadCoils:
fallthrough
case FunctionReadDiscreteInputs:
expectedLen = int(q.Quantity) / 8
if q.Quantity%8 != 0 {
expectedLen++
}
case FunctionReadInputRegisters:
fallthrough
case FunctionReadHoldingRegisters:
expectedLen = int(q.Quantity) * 2
}
if int(response[2]) != expectedLen {
return false, exceptions[exceptionBadResponseLength]
}
if len(response[3:]) != expectedLen {
return false, exceptions[exceptionResponseLengthMismatch]
}
}
return true, nil
}
// data is called by a Packager to construct the data payload for the Query and
// check if it IsValid().
func (q Query) data() ([]byte, error) {
if valid, err := q.IsValid(); !valid {
return nil, err
}
if isWriteFunction(q.FunctionCode) {
if isWriteMultipleFunction(q.FunctionCode) {
values := dataBlock(q.Values...)
if FunctionWriteMultipleCoils == q.FunctionCode {
numValues := q.Quantity / 8
if q.Quantity%8 != 0 {
numValues++
}
values = values[0:numValues]
}
return dataBlockSuffix(values, q.Address, q.Quantity), nil
}
if isWriteSingleFunction(q.FunctionCode) {
value := q.Values[0]
if q.FunctionCode == FunctionWriteSingleCoil && q.Values[0] != 0 {
value = 0xFF00
}
return dataBlock(q.Address, value), nil
}
if q.FunctionCode == FunctionMaskWriteRegister {
andMask := q.Values[0]
orMask := q.Values[1]
return dataBlock(q.Address, andMask, orMask), nil
}
}
// isReadFunction() must be true
return dataBlock(q.Address, q.Quantity), nil
}
// dataBlock creates a sequence of uint16 data.
func dataBlock(value ...uint16) []byte {
data := make([]byte, 2*len(value))
for i, v := range value {
binary.BigEndian.PutUint16(data[i*2:], v)
}
return data
}
// dataBlockSuffix creates a sequence of uint16 data and appends 1 byte for
// len(suffix) and then the suffix.
func dataBlockSuffix(suffix []byte, value ...uint16) []byte {
length := 2 * len(value)
data := make([]byte, length+1+len(suffix))
for i, v := range value {
binary.BigEndian.PutUint16(data[i*2:], v)
}
data[length] = uint8(len(suffix))
copy(data[length+1:], suffix)
return data
}
// ReadQuery constructs a Query where isReadFunction(fCode) is true.
func ReadQuery(slaveID byte, fCode FunctionCode,
address, quantity uint16) (Query, error) {
q := Query{
SlaveID: slaveID,
FunctionCode: fCode,
Address: address,
Quantity: quantity,
}
if !isReadFunction(fCode) {
return q, fmt.Errorf("Not a valid read function code")
}
_, err := q.IsValid()
return q, err
}
// WriteSingleQuery constructs a Query where isWriteSingleFunction(fCode) is
// true.
func WriteSingleQuery(slaveID byte, fCode FunctionCode,
address, value uint16) (Query, error) {
q := Query{
SlaveID: slaveID,
FunctionCode: fCode,
Address: address,
Values: []uint16{value},
}
if !isWriteSingleFunction(fCode) {
return q, fmt.Errorf("Not a single write function code")
}
_, err := q.IsValid()
return q, err
}
// WriteMultipleQuery constructs a Query where isWriteMultipleFunction(fCode)
// is true.
func WriteMultipleQuery(slaveID byte, fCode FunctionCode,
address, quantity uint16, values []uint16) (Query, error) {
q := Query{
SlaveID: slaveID,
FunctionCode: fCode,
Address: address,
Quantity: quantity,
Values: values,
}
if !isWriteMultipleFunction(fCode) {
return q, fmt.Errorf("Not a multiple write function code")
}
_, err := q.IsValid()
return q, err
}
// ReadCoils constructs a ReadCoils Query object.
func ReadCoils(slaveID byte, address, quantity uint16) (Query, error) {
return ReadQuery(slaveID, FunctionReadCoils, address, quantity)
}
// ReadDiscreteInputs constructs a ReadDiscreteInputs Query object.
func ReadDiscreteInputs(slaveID byte, address, quantity uint16) (Query, error) {
return ReadQuery(slaveID, FunctionReadDiscreteInputs, address, quantity)
}
// ReadHoldingRegisters constructs a ReadHoldingRegisters Query object.
func ReadHoldingRegisters(slaveID byte, address, quantity uint16) (Query, error) {
return ReadQuery(slaveID, FunctionReadHoldingRegisters, address, quantity)
}
// ReadInputRegisters constructs a ReadInputRegisters Query object.
func ReadInputRegisters(slaveID byte, address, quantity uint16) (Query, error) {
return ReadQuery(slaveID, FunctionReadInputRegisters, address, quantity)
}
// WriteSingleCoil constructs a WriteSingleCoil Query object.
func WriteSingleCoil(slaveID byte, address uint16, value bool) (Query, error) {
var valueData uint16
if value {
valueData = 0xFF00
}
return WriteSingleQuery(slaveID, FunctionWriteSingleCoil, address, valueData)
}
// WriteSingleRegister constructs a WriteSingleRegister Query object.
func WriteSingleRegister(slaveID byte, address, value uint16) (Query, error) {
return WriteSingleQuery(slaveID, FunctionWriteSingleRegister, address, value)
}
// WriteMultipleCoils constructs a WriteMultipleCoils Query object.
func WriteMultipleCoils(slaveID byte, address, quantity uint16,
value []uint16) (Query, error) {
return WriteMultipleQuery(slaveID, FunctionWriteMultipleCoils,
address, quantity, value)
}
// WriteMultipleRegisters constructs a WriteMultipleRegisters Query object.
func WriteMultipleRegisters(slaveID byte, address, quantity uint16,
value []uint16) (Query, error) {
return WriteMultipleQuery(slaveID, FunctionWriteMultipleRegisters,
address, quantity, value)
}
// MaskWriteRegister constructs a MaskWriteRegister Query object.
func MaskWriteRegister(slaveID byte, address, andMask, orMask uint16) (Query, error) {
q := Query{
SlaveID: slaveID,
FunctionCode: FunctionMaskWriteRegister,
Address: address,
Values: []uint16{andMask, orMask},
}
_, err := q.IsValid()
return q, err
}
// isReadFunction returns true if fCode is FunctionReadCoils,
// FunctionReadDiscreteInputs, FunctionReadHoldingRegisters, or
// FunctionReadInputRegisters.
func isReadFunction(fCode FunctionCode) bool {
switch fCode {
case FunctionReadCoils:
fallthrough
case FunctionReadDiscreteInputs:
fallthrough
case FunctionReadHoldingRegisters:
fallthrough
case FunctionReadInputRegisters:
return true
}
return false
}
// isWriteFunction returns true if fCode is FunctionWriteSingleCoil,
// FunctionWriteSingleRegister, FunctionWriteMultipleCoils,
// FunctionWriteMultipleRegisters, or FunctionMaskWriteRegister.
func isWriteFunction(fCode FunctionCode) bool {
switch fCode {
case FunctionWriteSingleCoil:
fallthrough
case FunctionWriteSingleRegister:
fallthrough
case FunctionWriteMultipleCoils:
fallthrough
case FunctionWriteMultipleRegisters:
fallthrough
case FunctionMaskWriteRegister:
return true
}
return false
}
// isWriteSingleFunction returns true if fCode is FunctionWriteSingleCoil or
// FunctionWriteSingleRegister
func isWriteSingleFunction(fCode FunctionCode) bool {
switch fCode {
case FunctionWriteSingleCoil:
fallthrough
case FunctionWriteSingleRegister:
return true
}
return false
}
// isWriteMultipleFunction returns true if fCode is FunctionWriteMultipleCoils
// or FunctionWriteMultipleRegisters.
func isWriteMultipleFunction(fCode FunctionCode) bool {
switch fCode {
case FunctionWriteMultipleCoils:
fallthrough
case FunctionWriteMultipleRegisters:
return true
}
return false
}