forked from simonvetter/modbus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tcp_server.go
337 lines (283 loc) · 9.88 KB
/
tcp_server.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
package main
import (
"fmt"
"os"
"math"
"sync"
"time"
"github.com/simonvetter/modbus"
)
const (
MINUS_ONE int16 = -1
)
/*
* Simple modbus server example.
*
* This file is intended to be a demo of the modbus server.
* It shows how to create and start a server, as well as how
* to write a handler object.
* Feel free to use it as boilerplate for simple servers.
*/
// run this with go run examples/tcp_server.go
func main() {
var server *modbus.ModbusServer
var err error
var eh *exampleHandler
var ticker *time.Ticker
// create the handler object
eh = &exampleHandler{}
// create the server object
server, err = modbus.NewServer(&modbus.ServerConfiguration{
// listen on localhost port 5502
URL: "tcp://localhost:5502",
// close idle connections after 30s of inactivity
Timeout: 30 * time.Second,
// accept 5 concurrent connections max.
MaxClients: 5,
}, eh)
if err != nil {
fmt.Printf("failed to create server: %v\n", err)
os.Exit(1)
}
// start accepting client connections
// note that Start() returns as soon as the server is started
err = server.Start()
if err != nil {
fmt.Printf("failed to start server: %v\n", err)
os.Exit(1)
}
// increment a 32-bit uptime counter every second.
// (this counter is exposed as input registers 200-201 for demo purposes)
ticker = time.NewTicker(1 * time.Second)
for {
<-ticker.C
// since the handler methods are called from multiple goroutines,
// use locking where appropriate to avoid concurrency issues.
eh.lock.Lock()
eh.uptime++
eh.lock.Unlock()
}
// never reached
return
}
// Example handler object, passed to the NewServer() constructor above.
type exampleHandler struct {
// this lock is used to avoid concurrency issues between goroutines, as
// handler methods are called from different goroutines
// (1 goroutine per client)
lock sync.RWMutex
// simple uptime counter, incremented in the main() above and exposed
// as a 32-bit input register (2 consecutive 16-bit modbus registers).
uptime uint32
// these are here to hold client-provided (written) values, for both coils and
// holding registers
coils [100]bool
holdingReg1 uint16
holdingReg2 uint16
// this is a 16-bit signed integer
holdingReg3 int16
// this is a 32-bit unsigned integer
holdingReg4 uint32
}
// Coil handler method.
// This method gets called whenever a valid modbus request asking for a coil operation is
// received by the server.
// It exposes 100 read/writable coils at addresses 0-99, except address 80 which is
// read-only.
// (read them with ./modbus-cli --target tcp://localhost:5502 rc:0+99, write to register n
// with ./modbus-cli --target tcp://localhost:5502 wr:n:<true|false>)
func (eh *exampleHandler) HandleCoils(unitId uint8, addr uint16, quantity uint16, isWrite bool, args []bool) (res []bool, err error) {
if unitId != 1 {
// only accept unit ID #1
// note: we're merely filtering here, but we could as well use the unit
// ID field to support multiple register maps in a single server.
err = modbus.ErrIllegalFunction
return
}
// make sure that all registers covered by this request actually exist
if int(addr) + int(quantity) > len(eh.coils) {
err = modbus.ErrIllegalDataAddress
return
}
// since we're manipulating variables shared between multiple goroutines,
// acquire a lock to avoid concurrency issues.
eh.lock.Lock()
// release the lock upon return
defer eh.lock.Unlock()
// loop through `quantity` registers, from address `addr` to
// `addr + quantity - 1`, which here is conveniently `addr + i`
for i := 0; i < int(quantity); i++ {
// ignore the write if the current register address is 80
if isWrite && int(addr) + i != 80 {
// assign the value
eh.coils[int(addr) + i] = args[i]
}
// append the value of the requested register to res so they can be
// sent back to the client
res = append(res, eh.coils[int(addr) + i])
}
return
}
// Discrete input handler method.
// Note that we're returning ErrIllegalFunction unconditionally.
// This will cause the client to receive "illegal function", which is the modbus way of
// reporting that this server does not support/implement the discrete input type.
func (eh *exampleHandler) HandleDiscreteInputs(unitId uint8, addr uint16, quantity uint16) (res []bool, err error) {
// this is the equivalent of saying
// "discrete inputs are not supported by this device"
// (try it with modbus-cli --target tcp://localhost:5502 rdi:1)
err = modbus.ErrIllegalFunction
return
}
// Holding register handler method.
// This method gets called whenever a valid modbus request asking for a holding register
// operation (either read or write) received by the server.
func (eh *exampleHandler) HandleHoldingRegisters(unitId uint8, addr uint16, quantity uint16, isWrite bool, args []uint16) (res []uint16, err error) {
var regAddr uint16
if unitId != 1 {
// only accept unit ID #1
err = modbus.ErrIllegalFunction
return
}
// since we're manipulating variables shared between multiple goroutines,
// acquire a lock to avoid concurrency issues.
eh.lock.Lock()
// release the lock upon return
defer eh.lock.Unlock()
// loop through `quantity` registers
for i := 0; i < int(quantity); i++ {
// compute the target register address
regAddr = addr + uint16(i)
switch regAddr {
// expose the static, read-only value of 0xff00 in register 100
case 100:
res = append(res, 0xff00)
// expose holdingReg1 in register 101 (RW)
case 101:
if isWrite {
eh.holdingReg1 = args[i]
}
res = append(res, eh.holdingReg1)
// expose holdingReg2 in register 102 (RW)
case 102:
if isWrite {
// only accept values 2 and 4
switch args[i] {
case 2, 4:
eh.holdingReg2 = args[i]
default:
// if the written value is neither 2 nor 4,
// return a modbus "illegal data value" to
// let the client know that the value is
// not acceptable.
err = modbus.ErrIllegalDataValue
return
}
}
res = append(res, eh.holdingReg2)
// expose eh.holdingReg3 in register 103 (RW)
// note: eh.holdingReg3 is a signed 16-bit integer
case 103:
if isWrite {
// cast the 16-bit unsigned integer passed by the server
// to a 16-bit signed integer when writing
eh.holdingReg3 = int16(args[i])
}
// cast the 16-bit signed integer from the handler to a 16-bit unsigned
// integer so that we can append it to `res`.
res = append(res, uint16(eh.holdingReg3))
// expose the 16 most-significant bits of eh.holdingReg4 in register 200
case 200:
if isWrite {
eh.holdingReg4 =
((uint32(args[i]) << 16) & 0xffff0000 |
(eh.holdingReg4 & 0x0000ffff))
}
res = append(res, uint16((eh.holdingReg4 >> 16) & 0x0000ffff))
// expose the 16 least-significant bits of eh.holdingReg4 in register 201
case 201:
if isWrite {
eh.holdingReg4 =
(uint32(args[i]) & 0x0000ffff |
(eh.holdingReg4 & 0xffff0000))
}
res = append(res, uint16(eh.holdingReg4 & 0x0000ffff))
// any other address is unknown
default:
err = modbus.ErrIllegalDataAddress
return
}
}
return
}
// Input register handler method.
// This method gets called whenever a valid modbus request asking for an input register
// operation is received by the server.
// Note that input registers are always read-only as per the modbus spec.
func (eh *exampleHandler) HandleInputRegisters(unitId uint8, addr uint16, quantity uint16) (res []uint16, err error) {
var unixTs_s uint32
var minusOne int16 = -1
if unitId != 1 {
// only accept unit ID #1
err = modbus.ErrIllegalFunction
return
}
// get the current unix timestamp, converted as a 32-bit unsigned integer for
// simplicity
unixTs_s = uint32(time.Now().Unix() & 0xffffffff)
// loop through all register addresses from addr to addr + quantity - 1
for regAddr := addr; regAddr < addr + quantity; regAddr++ {
switch regAddr {
case 100:
// return the static value 0x1111 at address 100, as an unsigned
// 16-bit integer
// (read it with modbus-cli --target tcp://localhost:5502 ri:uint16:100)
res = append(res, 0x1111)
case 101:
// return the static value -1 at address 101, as a signed 16-bit
// integer
// (read it with modbus-cli --target tcp://localhost:5502 ri:int16:101)
res = append(res, uint16(minusOne))
// expose our uptime counter, encoded as a 32-bit unsigned integer in
// input registers 200-201
// (read it with modbus-cli --target tcp://localhost:5502 ri:uint32:200)
case 200:
// return the 16 most significant bits of the uptime counter
// (using locking to avoid concurrency issues)
eh.lock.RLock()
res = append(res, uint16((eh.uptime >> 16) & 0xffff))
eh.lock.RUnlock()
case 201:
// return the 16 least significant bits of the uptime counter
// (again, using locking to avoid concurrency issues)
eh.lock.RLock()
res = append(res, uint16(eh.uptime & 0xffff))
eh.lock.RUnlock()
// expose the current unix timestamp, encoded as a 32-bit unsigned integer
// in input registers 202-203
// (read it with modbus-cli --target tcp://localhost:5502 ri:uint32:202)
case 202:
// return the 16 most significant bits of the current unix time
res = append(res, uint16((unixTs_s >> 16) & 0xffff))
case 203:
// return the 16 least significant bits of the current unix time
res = append(res, uint16(unixTs_s & 0xffff))
// return 3.1415, encoded as a 32-bit floating point number in input
// registers 300-301
// (read it with modbus-cli --target tcp://localhost:5502 ri:float32:300)
case 300:
// returh the 16 most significant bits of the number
res = append(res, uint16((math.Float32bits(3.1415) >> 16) & 0xffff))
case 301:
// returh the 16 least significant bits of the number
res = append(res, uint16((math.Float32bits(3.1415)) & 0xffff))
// attempting to access any input register address other than
// those defined above will result in an illegal data address
// exception client-side.
default:
err = modbus.ErrIllegalDataAddress
return
}
}
return
}