-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmessage.go
302 lines (237 loc) · 7.71 KB
/
message.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
package modbus
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
)
const (
// ReadCoils is Modbus function code 1.
ReadCoils uint8 = iota + 1
// ReadDiscreteInputs is Modbus function code 2.
ReadDiscreteInputs
// ReadHoldingRegisters is Modbus function code 3.
ReadHoldingRegisters
// ReadInputRegisters is Modbus function code 4.
ReadInputRegisters
// WriteSingleCoil is Modbus function code 5.
WriteSingleCoil
// WriteSingleRegister is Modbus function code 6.
WriteSingleRegister
// WriteMultipleCoils is Modbus function code 15.
WriteMultipleCoils = 15 + 1
// WriteMultipleRegisters is Modbus function code 16.
WriteMultipleRegisters
)
// Error represesents a Modbus protocol error.
type Error struct {
// Code contains the Modbus exception code.
Code uint8
msg string
}
func (e Error) Error() string {
return fmt.Sprintf("Modbus exception code: %d: %v", e.Code, e.msg)
}
var (
// IllegalFunctionError with exception code 1, is returned when the
// function received is not an allowable action fwith the slave.
IllegalFunctionError = Error{Code: 1, msg: "illegal function"}
// IllegalAddressError with exception code 2, is returned when the
// address received is not an allowable address fwith the slave.
IllegalAddressError = Error{Code: 2, msg: "illegal address"}
// IllegalDataValueError with exception code 3, is returned if the
// request contains an value that is not allowable fwith the slave.
IllegalDataValueError = Error{Code: 3, msg: "illegal data value"}
// SlaveDeviceFailureError with exception code 4, is returned when
// the server isn't able to handle the request.
SlaveDeviceFailureError = Error{Code: 4, msg: "slave device failure"}
// AcknowledgeError with exception code 5, is returned when the
// server has received the request successfully, but needs a long time
// to process the request.
AcknowledgeError = Error{Code: 5, msg: "acknowledge"}
// SlaveDeviceBusyError with exception 6, is returned when master is
// busy processing a long-running command.
SlaveDeviceBusyError = Error{Code: 6, msg: "slave device busy"}
// NegativeAcknowledgeError with exception code 7, is returned for an
// unsuccessful programming request using function code 13 or 14.
NegativeAcknowledgeError = Error{Code: 7, msg: "negative acknowledge"}
// MemoryParityError with exception code 8 is returned to indicate that
// the extended file area failed to pass a consistency check. May only
// returned for requests with function code 20 or 21.
MemoryParityError = Error{Code: 8, msg: "memory parity error"}
// GatewayPathUnavailableError with exception code 10 indicates that
// the gateway was unable to allocate an internal communication path
// from the input port to the output port for processing the request.
GatewayPathUnavailableError = Error{Code: 10, msg: "gateway path unavailable"}
// GatewayTargetDeviceFailedToRespondError with exception code 11
// indicates that the device is not present on the network.
GatewayTargetDeviceFailedToRespondError = Error{Code: 11, msg: "gateway target device failed to respond"}
)
// Value is a value an integer ranging from range of -32768 through 65535.
type Value struct {
v int
}
// NewValue creates a Value. It returns an error when given value is outside
// range of -32768 through 65535.
func NewValue(v int) (Value, error) {
var value Value
if err := value.Set(v); err != nil {
return value, err
}
return value, nil
}
// Set sets the value. It returns an error when given value is outside range of
// -32768 through 65535.
func (v *Value) Set(value int) error {
if value < -32768 || value > 65535 {
return fmt.Errorf("%d doesn't fit in 16 bytes", value)
}
v.v = value
return nil
}
// Get returns the value.
func (v *Value) Get() int {
return v.v
}
// MarshalBinary marshals a Value into byte slice with length of 2
// bytes.
func (v *Value) MarshalBinary() ([]byte, error) {
buf := new(bytes.Buffer)
var value interface{}
if v.v < 0 {
value = int16(v.v)
} else {
value = uint16(v.v)
}
if err := binary.Write(buf, binary.BigEndian, value); err != nil {
return buf.Bytes(), fmt.Errorf("failed to marshal ResponseValue: %v", err)
}
return buf.Bytes(), nil
}
// UnmarshalBinary value from a byte slice.
func (v *Value) UnmarshalBinary(d []byte, s Signedness) error {
if len(d) != 2 {
return errors.New("can't unmarshal value: byte slice isn't of length 2")
}
v.v = int(int16(binary.BigEndian.Uint16(d)))
if s == Unsigned {
v.v = int(binary.BigEndian.Uint16(d))
}
return nil
}
// MBAP is the Modbus Application Header. Only Modbus TCP/IP message have an
// MBAP header. The MBAP header has 4 fields with a total length of 7 bytes.
type MBAP struct {
// TransactionID identifies a request/response transaction.
TransactionID uint16
// ProtocolID defines the protocol, for Modbus it's always 0.
ProtocolID uint16
// Length shows how much bytes are following.
Length uint16
// UnitID or slave id identifies a slave.
UnitID uint8
}
// UnmarshalBinary unmarshals a binary representation of MBAP.
func (m *MBAP) UnmarshalBinary(b []byte) error {
if len(b) != 7 {
return fmt.Errorf("failed to unmarshal byte slice to MBAP: byte slice has invalid length of %d", len(b))
}
m.TransactionID = binary.BigEndian.Uint16(b[:2])
m.ProtocolID = binary.BigEndian.Uint16(b[2:4])
m.Length = binary.BigEndian.Uint16(b[4:6])
m.UnitID = uint8(b[6])
return nil
}
// MarshalBinary marshals a MBAP to it binary form.
func (m *MBAP) MarshalBinary() ([]byte, error) {
buf := new(bytes.Buffer)
data := []interface{}{
m.TransactionID,
m.ProtocolID,
m.Length,
m.UnitID,
}
for _, v := range data {
err := binary.Write(buf, binary.BigEndian, v)
if err != nil {
return buf.Bytes(), fmt.Errorf("failed to marshal MBAP to binary form: %v", err)
}
}
return buf.Bytes(), nil
}
// Request is a Modbus request.
type Request struct {
// Request is a Modbus request.
MBAP
FunctionCode uint8
Data []byte
}
// UnmarshalBinary unmarshals binary representation of Request.
func (r *Request) UnmarshalBinary(b []byte) error {
if err := r.MBAP.UnmarshalBinary(b[0:7]); err != nil {
return err
}
r.FunctionCode = uint8(b[7])
r.Data = b[8:]
return nil
}
// Response is a Modbus response.
type Response struct {
MBAP
FunctionCode uint8
Data []byte
exception bool
}
// NewResponse creates a Response for a Request.
func NewResponse(r Request, data []byte) *Response {
resp := &Response{
MBAP: r.MBAP,
FunctionCode: r.FunctionCode,
Data: data,
}
resp.MBAP.Length = uint16(len(data) + 3)
switch r.FunctionCode {
case WriteSingleCoil, WriteSingleRegister, WriteMultipleRegisters:
resp.MBAP.Length = uint16(len(data) + 2)
}
return resp
}
// NewErrorResponse creates a error response.
func NewErrorResponse(r Request, err error) *Response {
resp := &Response{
MBAP: r.MBAP,
FunctionCode: r.FunctionCode + 0x80,
exception: true,
}
resp.Data = []byte{5}
if err, ok := err.(Error); ok {
resp.Data = []byte{err.Code}
}
resp.MBAP.Length = 3
return resp
}
// MarshalBinary marshals a Response to it binary form.
func (r *Response) MarshalBinary() ([]byte, error) {
mbap, err := r.MBAP.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal response to its binary form: %v", err)
}
pdu := new(bytes.Buffer)
data := []interface{}{
r.FunctionCode,
}
switch r.FunctionCode {
case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
if !r.exception {
data = append(data, uint8(len(r.Data)))
}
}
data = append(data, r.Data)
for _, v := range data {
err := binary.Write(pdu, binary.BigEndian, v)
if err != nil {
return nil, fmt.Errorf("failed to marshal response to its binary form: %v", err)
}
}
return append(mbap, pdu.Bytes()...), nil
}