rmodbus 1.3.2 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/NEWS.md +19 -0
- data/README.md +8 -8
- data/examples/perfomance_rtu.rb +55 -56
- data/examples/perfomance_rtu_via_tcp.rb +54 -55
- data/examples/perfomance_tcp.rb +54 -55
- data/examples/simple_xpca_gateway.rb +85 -0
- data/examples/use_rtu_via_tcp_modbus.rb +14 -11
- data/examples/use_tcp_modbus.rb +14 -11
- data/lib/rmodbus/client/slave.rb +333 -0
- data/lib/rmodbus/client.rb +15 -10
- data/lib/rmodbus/debug.rb +12 -15
- data/lib/rmodbus/errors.rb +26 -2
- data/lib/rmodbus/ext.rb +72 -51
- data/lib/rmodbus/options.rb +4 -1
- data/lib/rmodbus/proxy.rb +14 -9
- data/lib/rmodbus/rtu.rb +89 -125
- data/lib/rmodbus/rtu_client.rb +22 -2
- data/lib/rmodbus/rtu_server.rb +16 -12
- data/lib/rmodbus/rtu_slave.rb +26 -3
- data/lib/rmodbus/rtu_via_tcp_server.rb +12 -19
- data/lib/rmodbus/server/slave.rb +18 -0
- data/lib/rmodbus/server.rb +227 -84
- data/lib/rmodbus/sp.rb +10 -12
- data/lib/rmodbus/tcp.rb +9 -10
- data/lib/rmodbus/tcp_client.rb +3 -0
- data/lib/rmodbus/tcp_server.rb +41 -35
- data/lib/rmodbus/tcp_slave.rb +19 -18
- data/lib/rmodbus/version.rb +3 -2
- data/lib/rmodbus.rb +20 -21
- metadata +63 -50
- data/Rakefile +0 -29
- data/examples/simple-xpca-gateway.rb +0 -84
- data/lib/rmodbus/rtu_via_tcp_client.rb +0 -26
- data/lib/rmodbus/rtu_via_tcp_slave.rb +0 -29
- data/lib/rmodbus/slave.rb +0 -308
- data/spec/client_spec.rb +0 -88
- data/spec/exception_spec.rb +0 -119
- data/spec/ext_spec.rb +0 -52
- data/spec/logging_spec.rb +0 -89
- data/spec/proxy_spec.rb +0 -74
- data/spec/read_rtu_response_spec.rb +0 -92
- data/spec/response_mismach_spec.rb +0 -163
- data/spec/rtu_client_spec.rb +0 -86
- data/spec/rtu_server_spec.rb +0 -30
- data/spec/rtu_via_tcp_client_spec.rb +0 -76
- data/spec/rtu_via_tcp_server_spec.rb +0 -16
- data/spec/slave_spec.rb +0 -55
- data/spec/spec_helper.rb +0 -54
- data/spec/tcp_client_spec.rb +0 -88
- data/spec/tcp_server_spec.rb +0 -129
data/lib/rmodbus/proxy.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# Given a slave and a type of operation, execute a single or multiple read using hash syntax
|
3
5
|
class ReadOnlyProxy
|
@@ -10,9 +12,9 @@ module ModBus
|
|
10
12
|
# Note that in the case of multiples, a pluralized version of the method is sent to the slave
|
11
13
|
def [](key)
|
12
14
|
if key.instance_of?(0.class)
|
13
|
-
@slave.send("read_#{@type}", key
|
15
|
+
@slave.send(:"read_#{@type}", key)
|
14
16
|
elsif key.instance_of?(Range)
|
15
|
-
@slave.send("read_#{@type}s", key.first, key.count)
|
17
|
+
@slave.send(:"read_#{@type}s", key.first, key.count)
|
16
18
|
else
|
17
19
|
raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
18
20
|
end
|
@@ -20,22 +22,25 @@ module ModBus
|
|
20
22
|
end
|
21
23
|
|
22
24
|
class ReadWriteProxy < ReadOnlyProxy
|
23
|
-
# Write single or multiple values to a modbus slave depending on whether a
|
24
|
-
#
|
25
|
-
#
|
25
|
+
# Write single or multiple values to a modbus slave depending on whether a
|
26
|
+
# Fixnum or a Range was given.
|
27
|
+
# Note that in the case of multiples, a pluralized version of the method is
|
28
|
+
# sent to the slave. Also when writing multiple values, the number of
|
29
|
+
# elements must match the number of registers in the range or an exception
|
30
|
+
# is raised
|
26
31
|
def []=(key, val)
|
27
32
|
if key.instance_of?(0.class)
|
28
|
-
@slave.send("write_#{@type}", key, val)
|
33
|
+
@slave.send(:"write_#{@type}", key, val)
|
29
34
|
elsif key.instance_of?(Range)
|
30
35
|
if key.count != val.size
|
31
|
-
raise ModBus::Errors::ProxyException,
|
36
|
+
raise ModBus::Errors::ProxyException,
|
37
|
+
"The size of the range must match the size of the values (#{key.count} != #{val.size})"
|
32
38
|
end
|
33
39
|
|
34
|
-
@slave.send("write_#{@type}s", key.first, val)
|
40
|
+
@slave.send(:"write_#{@type}s", key.first, val)
|
35
41
|
else
|
36
42
|
raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
40
|
-
|
41
46
|
end
|
data/lib/rmodbus/rtu.rb
CHANGED
@@ -1,31 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/crc16_modbus"
|
4
|
+
require "io/wait"
|
5
|
+
|
1
6
|
module ModBus
|
2
7
|
module RTU
|
3
8
|
private
|
4
9
|
|
5
10
|
# We have to read specific amounts of numbers of bytes from the network depending on the function code and content
|
6
11
|
def read_rtu_response(io)
|
7
|
-
|
8
|
-
msg =
|
9
|
-
while msg.nil?
|
10
|
-
msg = io.read(2)
|
11
|
-
end
|
12
|
+
# Read the slave_id and function code
|
13
|
+
msg = read(io, 2)
|
12
14
|
|
13
15
|
function_code = msg.getbyte(1)
|
14
16
|
case function_code
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
when 1, 2, 3, 4
|
18
|
+
# read the third byte to find out how much more
|
19
|
+
# we need to read + CRC
|
20
|
+
msg += read(io, 1)
|
21
|
+
msg + read(io, msg.getbyte(2) + 2)
|
22
|
+
when 5, 6, 15, 16
|
23
|
+
# We just read in an additional 6 bytes
|
24
|
+
msg + read(io, 6)
|
25
|
+
when 22
|
26
|
+
msg + read(io, 8)
|
27
|
+
when 0x80..0xff
|
28
|
+
msg + read(io, 3)
|
29
|
+
else
|
30
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -38,127 +40,89 @@ module ModBus
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
def read(io, len)
|
44
|
+
result = +""
|
45
|
+
loop do
|
46
|
+
this_iter = io.read(len - result.length)
|
47
|
+
result.concat(this_iter) if this_iter
|
48
|
+
return result if result.length == len
|
47
49
|
|
48
|
-
|
50
|
+
io.wait_readable
|
51
|
+
end
|
49
52
|
end
|
50
53
|
|
51
|
-
def
|
52
|
-
|
54
|
+
def read_rtu_request(io)
|
55
|
+
# Every message is a minimum of 4 bytes (slave id, function code, crc16)
|
56
|
+
msg = read(io, 4)
|
53
57
|
|
54
|
-
|
58
|
+
# If msg is nil, then our client never sent us anything and it's time to disconnect
|
59
|
+
return if msg.nil?
|
55
60
|
|
56
|
-
if msg.getbyte(0) == @uid
|
57
|
-
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
58
|
-
log "Ignore package: don't match CRC"
|
59
|
-
else
|
60
|
-
log "Ignore package: don't match uid ID"
|
61
|
-
end
|
62
61
|
loop do
|
63
|
-
|
64
|
-
|
62
|
+
offset = 0
|
63
|
+
crc = msg[-2..].unpack1("S<")
|
64
|
+
|
65
|
+
# scan the bytestream for a valid CRC
|
66
|
+
loop do
|
67
|
+
break if offset >= msg.length - 3
|
68
|
+
|
69
|
+
calculated_crc = Digest::CRC16Modbus.checksum(msg[offset..-3])
|
70
|
+
if crc == calculated_crc
|
71
|
+
is_response = (msg.getbyte(offset + 1) & 0x80 == 0x80) ||
|
72
|
+
(msg.getbyte(offset) == @last_req_uid &&
|
73
|
+
msg.getbyte(offset + 1) == @last_req_func &&
|
74
|
+
@last_req_timestamp && Time.now.to_f - @last_req_timestamp < 5)
|
75
|
+
|
76
|
+
params = if is_response
|
77
|
+
parse_response(msg.getbyte(offset + 1), msg[(offset + 1)..-3])
|
78
|
+
else
|
79
|
+
parse_request(msg.getbyte(offset + 1), msg[(offset + 1)..-3])
|
80
|
+
end
|
81
|
+
|
82
|
+
unless params.nil?
|
83
|
+
if is_response
|
84
|
+
@last_req_uid = @last_req_func = @last_req_timestamp = nil
|
85
|
+
else
|
86
|
+
@last_req_uid = msg.getbyte(offset)
|
87
|
+
@last_req_func = msg.getbyte(offset + 1)
|
88
|
+
@last_req_timestamp = Time.now.to_f
|
89
|
+
end
|
90
|
+
log "Server RX discarding #{offset} bytes: #{logging_bytes(msg[0...offset])}" if offset != 0
|
91
|
+
log "Server RX (#{msg.size - offset} bytes): #{logging_bytes(msg[offset..])}"
|
92
|
+
return [msg.getbyte(offset), msg.getbyte(offset + 1), params, msg[offset + 1..-3], is_response]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
offset += 1
|
96
|
+
end
|
97
|
+
|
98
|
+
msg.concat(read(io, 1))
|
99
|
+
# maximum message size is 256, so that's as far as we have to
|
100
|
+
# be able to see at once
|
101
|
+
msg = msg[1..] if msg.length > 256
|
65
102
|
end
|
66
103
|
end
|
67
104
|
|
68
|
-
def
|
69
|
-
# Read the slave_id and function code
|
70
|
-
msg = io.read(2)
|
71
|
-
|
72
|
-
# If msg is nil, then our client never sent us anything and it's time to disconnect
|
73
|
-
return if msg.nil?
|
74
|
-
|
75
|
-
function_code = msg.getbyte(1)
|
76
|
-
if [1, 2, 3, 4, 5, 6].include?(function_code)
|
77
|
-
# read 6 more bytes and return the message total message
|
78
|
-
msg += io.read(6)
|
79
|
-
elsif [15, 16].include?(function_code)
|
80
|
-
# Read in first register, register count, and data bytes
|
81
|
-
msg += io.read(5)
|
82
|
-
# Read in however much data we need to + 2 CRC bytes
|
83
|
-
msg += io.read(msg.getbyte(6) + 2)
|
84
|
-
else
|
85
|
-
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
86
|
-
end
|
87
|
-
|
88
|
-
log "Server RX (#{msg.size} bytes): #{logging_bytes(msg)}"
|
89
|
-
|
90
|
-
msg
|
91
|
-
end
|
92
|
-
|
93
|
-
def serv_rtu_requests(io, &blk)
|
105
|
+
def serve(io)
|
94
106
|
loop do
|
95
107
|
# read the RTU message
|
96
|
-
|
97
|
-
|
98
|
-
next if msg.nil?
|
99
|
-
|
100
|
-
if msg.getbyte(0) == @uid and msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
101
|
-
pdu = yield msg
|
102
|
-
resp = @uid.chr + pdu
|
103
|
-
resp << crc16(resp).to_word
|
104
|
-
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
105
|
-
io.write resp
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
108
|
+
uid, func, params, pdu, is_response = read_rtu_request(io)
|
109
109
|
|
110
|
-
|
111
|
-
def crc16(msg)
|
112
|
-
crc_lo = 0xff
|
113
|
-
crc_hi = 0xff
|
110
|
+
next if uid.nil?
|
114
111
|
|
115
|
-
|
116
|
-
|
117
|
-
crc_hi = crc_lo ^ CrcHiTable[i]
|
118
|
-
crc_lo = CrcLoTable[i]
|
119
|
-
end
|
112
|
+
pdu = exec_req(uid, func, params, pdu, is_response: is_response)
|
113
|
+
next unless pdu
|
120
114
|
|
121
|
-
|
115
|
+
@last_req_uid = @last_req_func = @last_req_timestamp = nil
|
116
|
+
resp = uid.chr + pdu
|
117
|
+
resp << [crc16(resp)].pack("S<")
|
118
|
+
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
119
|
+
io.write resp
|
120
|
+
end
|
122
121
|
end
|
123
122
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
129
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
|
130
|
-
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
131
|
-
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
132
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
133
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
134
|
-
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
135
|
-
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
136
|
-
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
137
|
-
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
138
|
-
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
139
|
-
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
140
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
141
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
142
|
-
0x40]
|
143
|
-
CrcLoTable = [
|
144
|
-
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
|
145
|
-
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
146
|
-
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
|
147
|
-
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
148
|
-
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
|
149
|
-
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
150
|
-
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
|
151
|
-
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
152
|
-
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
|
153
|
-
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
154
|
-
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
|
155
|
-
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
156
|
-
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
|
157
|
-
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
158
|
-
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
|
159
|
-
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
160
|
-
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
161
|
-
0x40]
|
162
|
-
|
123
|
+
# Calc CRC16 for massage
|
124
|
+
def crc16(msg)
|
125
|
+
Digest::CRC16Modbus.checksum(msg)
|
126
|
+
end
|
163
127
|
end
|
164
128
|
end
|
data/lib/rmodbus/rtu_client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# RTU client implementation
|
3
5
|
# @example
|
@@ -7,16 +9,34 @@ module ModBus
|
|
7
9
|
# end
|
8
10
|
# end
|
9
11
|
#
|
12
|
+
# @example
|
13
|
+
# RTUClient.connect('127.0.0.1', 10002) do |cl|
|
14
|
+
# cl.with_slave(uid) do |slave|
|
15
|
+
# slave.holding_registers[0..100]
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# @see TCP#open_tcp_connection
|
10
20
|
# @see SP#open_serial_port
|
11
21
|
# @see Client#initialize
|
12
22
|
class RTUClient < Client
|
13
23
|
include RTU
|
14
24
|
include SP
|
25
|
+
include TCP
|
15
26
|
|
16
27
|
protected
|
28
|
+
|
17
29
|
# Open serial port
|
18
|
-
def open_connection(
|
19
|
-
|
30
|
+
def open_connection(port_or_ipaddr, arg = nil, opts = {})
|
31
|
+
if port_or_ipaddr.is_a?(IO) || port_or_ipaddr.respond_to?(:read)
|
32
|
+
port_or_ipaddr
|
33
|
+
elsif File.exist?(port_or_ipaddr) || port_or_ipaddr.start_with?("/dev") || port_or_ipaddr.start_with?("COM")
|
34
|
+
arg ||= 9600
|
35
|
+
open_serial_port(port_or_ipaddr, arg, opts)
|
36
|
+
else
|
37
|
+
arg ||= 10_002
|
38
|
+
open_tcp_connection(port_or_ipaddr, arg, opts)
|
39
|
+
end
|
20
40
|
end
|
21
41
|
|
22
42
|
def get_slave(uid, io)
|
data/lib/rmodbus/rtu_server.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# RTU server implementation
|
3
5
|
# @example
|
4
|
-
# srv = RTUServer.new('/dev/ttyS1', 9600
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
6
|
+
# srv = RTUServer.new('/dev/ttyS1', 9600)
|
7
|
+
# slave = srv.with_slave(1)
|
8
|
+
# slave.coils = [1,0,1,1]
|
9
|
+
# slave.discrete_inputs = [1,1,0,0]
|
10
|
+
# slave.holding_registers = [1,2,3,4]
|
11
|
+
# slave.input_registers = [1,2,3,4]
|
12
|
+
# srv.logger = Logger.new($stdout)
|
10
13
|
# srv.start
|
11
14
|
class RTUServer
|
12
15
|
include Debug
|
@@ -17,18 +20,19 @@ module ModBus
|
|
17
20
|
# Init RTU server
|
18
21
|
# @param [Integer] uid slave device
|
19
22
|
# @see SP#open_serial_port
|
20
|
-
def initialize(port, baud=9600,
|
23
|
+
def initialize(port, baud = 9600, opts = {})
|
21
24
|
Thread.abort_on_exception = true
|
22
|
-
@sp =
|
23
|
-
|
25
|
+
@sp = if port.is_a?(IO) || port.respond_to?(:read)
|
26
|
+
port
|
27
|
+
else
|
28
|
+
open_serial_port(port, baud, opts)
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
# Start server
|
27
33
|
def start
|
28
34
|
@serv = Thread.new do
|
29
|
-
|
30
|
-
exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
31
|
-
end
|
35
|
+
serve(@sp)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
data/lib/rmodbus/rtu_slave.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# RTU slave implementation
|
3
5
|
# @example
|
@@ -10,20 +12,41 @@ module ModBus
|
|
10
12
|
# @see RTUClient#open_connection
|
11
13
|
# @see Client#with_slave
|
12
14
|
# @see Slave
|
13
|
-
class RTUSlave < Slave
|
15
|
+
class RTUSlave < Client::Slave
|
14
16
|
include RTU
|
15
17
|
|
16
18
|
private
|
19
|
+
|
17
20
|
# overide method for RTU implamentaion
|
18
21
|
# @see Slave#query
|
19
22
|
def send_pdu(pdu)
|
20
|
-
|
23
|
+
msg = @uid.chr + pdu
|
24
|
+
msg << [crc16(msg)].pack("S<")
|
25
|
+
|
26
|
+
clean_input_buff
|
27
|
+
@io.write msg
|
28
|
+
|
29
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
21
30
|
end
|
22
31
|
|
23
32
|
# overide method for RTU implamentaion
|
24
33
|
# @see Slave#query
|
25
34
|
def read_pdu
|
26
|
-
|
35
|
+
msg = read_rtu_response(@io)
|
36
|
+
|
37
|
+
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
38
|
+
|
39
|
+
if msg.getbyte(0) == @uid
|
40
|
+
return msg[1..-3] if msg[-2, 2].unpack1("S<") == crc16(msg[0..-3])
|
41
|
+
|
42
|
+
log "Ignore package: don't match CRC"
|
43
|
+
else
|
44
|
+
log "Ignore package: don't match uid ID"
|
45
|
+
end
|
46
|
+
loop do
|
47
|
+
# waite timeout
|
48
|
+
sleep(0.1)
|
49
|
+
end
|
27
50
|
end
|
28
51
|
end
|
29
52
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
|
-
require
|
4
|
+
require "gserver"
|
3
5
|
rescue
|
4
6
|
warn "[WARNING] Install `gserver` gem for use RTUViaTCPServer"
|
5
7
|
end
|
@@ -7,12 +9,13 @@ end
|
|
7
9
|
module ModBus
|
8
10
|
# RTU over TCP server implementation
|
9
11
|
# @example
|
10
|
-
# srv = RTUViaTCPServer.new(10002
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
12
|
+
# srv = RTUViaTCPServer.new(10002)
|
13
|
+
# slave = src.with_slave(1)
|
14
|
+
# slave.coils = [1,0,1,1]
|
15
|
+
# slave.discrete_inputs = [1,1,0,0]
|
16
|
+
# slave.holding_registers = [1,2,3,4]
|
17
|
+
# slave.input_registers = [1,2,3,4]
|
18
|
+
# srv.logger = Logger.new($stdout)
|
16
19
|
# srv.start
|
17
20
|
class RTUViaTCPServer < GServer
|
18
21
|
include Debug
|
@@ -25,20 +28,10 @@ module ModBus
|
|
25
28
|
# @param [Hash] opts options of server
|
26
29
|
# @option opts [String] :host host of server default '127.0.0.1'
|
27
30
|
# @option opts [Float, Integer] :max_connection max of TCP connection with server default 4
|
28
|
-
def initialize(port =
|
29
|
-
@uid = uid
|
31
|
+
def initialize(port = 10_002, opts = {})
|
30
32
|
opts[:host] = DEFAULT_HOST unless opts[:host]
|
31
33
|
opts[:max_connection] = 4 unless opts[:max_connection]
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
protected
|
36
|
-
# Serve requests
|
37
|
-
# @param [TCPSocket] io socket
|
38
|
-
def serve(io)
|
39
|
-
serv_rtu_requests(io) do |msg|
|
40
|
-
exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
41
|
-
end
|
34
|
+
super(port, opts[:host], opts[:max_connection])
|
42
35
|
end
|
43
36
|
end
|
44
37
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
module ModBus
|
6
|
+
module Server
|
7
|
+
class Slave
|
8
|
+
attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@coils = []
|
12
|
+
@discrete_inputs = []
|
13
|
+
@holding_registers = []
|
14
|
+
@input_registers = []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|