rmodbus 1.3.3 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 -310
- 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
|