-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRTUPackager.go
126 lines (104 loc) · 2.48 KB
/
RTUPackager.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
package modbus
import (
"encoding/binary"
"errors"
"log"
"time"
"github.com/tarm/serial"
)
// RTUPackager implements the Packager interface for Modbus RTU.
type RTUPackager struct {
packagerSettings
*serial.Port
}
// NewRTUPackager returns a new, ready to use RTUPackager with the given
// ConnectionSettings.
func NewRTUPackager(c ConnectionSettings) (*RTUPackager, error) {
p, err := newSerialPort(c)
if nil != err {
return nil, err
}
return &RTUPackager{
Port: p,
packagerSettings: packagerSettings{
Debug: c.Debug,
}}, nil
}
func (pkgr *RTUPackager) generateADU(q Query) ([]byte, error) {
data, err := q.data()
if err != nil {
return nil, err
}
if q.SlaveID == 0 {
return nil, errors.New("SlaveID cannot be 0 for Modbus RTU")
}
packetLen := len(data) + 4
packet := make([]byte, packetLen)
packet[0] = q.SlaveID
packet[1] = byte(q.FunctionCode)
bytesUsed := 2
bytesUsed += copy(packet[bytesUsed:], data)
// add the crc to the end
packetCrc := crc(packet[:bytesUsed])
packet[bytesUsed] = byte(packetCrc & 0xff)
packet[bytesUsed+1] = byte(packetCrc >> 8)
return packet, nil
}
// Send sends the Query and returns the result or and error code.
func (pkgr *RTUPackager) Send(q Query) ([]byte, error) {
adu, err := pkgr.generateADU(q)
if err != nil {
return nil, err
}
if pkgr.Debug {
log.Printf("Tx: %x\n", adu)
}
_, err = pkgr.Write(adu)
if err != nil {
return nil, err
}
time.Sleep(20 * time.Millisecond)
response := make([]byte, MaxRTUSize)
n, rerr := pkgr.Read(response)
if rerr != nil {
return nil, rerr
}
if pkgr.Debug {
log.Printf("Rx Full: %x\n", response)
}
// Confirm the checksum
computedCrc := crc(response[:n-2])
if computedCrc != binary.LittleEndian.Uint16(response[n-2:]) {
return nil, exceptions[exceptionBadChecksum]
}
response = response[:n-2]
if pkgr.Debug {
log.Printf("Rx: %x\n", response)
}
// Check the validity of the response
if valid, err := q.isValidResponse(response); !valid {
return nil, err
}
// Return only the data payload
if isReadFunction(q.FunctionCode) {
return response[3:], nil
}
// Return only the data payload
return response[2:], nil
}
// crc computes and returns a cyclic redundancy check of the given byte array.
func crc(data []byte) uint16 {
var crc16 uint16 = 0xffff
l := len(data)
for i := 0; i < l; i++ {
crc16 ^= uint16(data[i])
for j := 0; j < 8; j++ {
if crc16&0x0001 > 0 {
crc16 = (crc16 >> 1) ^ 0xA001
} else {
crc16 >>= 1
}
}
}
return crc16
}