rmodbus-ccutrer 2.0.0 → 2.1.1
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 +4 -4
- data/NEWS.md +14 -7
- 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 +58 -70
- data/lib/rmodbus/client.rb +13 -10
- data/lib/rmodbus/debug.rb +10 -6
- 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 +38 -32
- data/lib/rmodbus/rtu_client.rb +5 -2
- data/lib/rmodbus/rtu_server.rb +9 -7
- data/lib/rmodbus/rtu_slave.rb +6 -2
- data/lib/rmodbus/rtu_via_tcp_server.rb +7 -5
- data/lib/rmodbus/server/slave.rb +4 -2
- data/lib/rmodbus/server.rb +97 -73
- data/lib/rmodbus/sp.rb +10 -12
- data/lib/rmodbus/tcp.rb +5 -2
- data/lib/rmodbus/tcp_client.rb +3 -0
- data/lib/rmodbus/tcp_server.rb +28 -26
- data/lib/rmodbus/tcp_slave.rb +17 -16
- data/lib/rmodbus/version.rb +3 -1
- data/lib/rmodbus.rb +20 -18
- metadata +50 -49
- data/Rakefile +0 -29
- data/examples/simple-xpca-gateway.rb +0 -84
- data/spec/client_spec.rb +0 -88
- data/spec/exception_spec.rb +0 -120
- 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 -31
- data/spec/rtu_via_tcp_client_spec.rb +0 -76
- data/spec/rtu_via_tcp_server_spec.rb +0 -89
- 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 -158
data/lib/rmodbus/rtu.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/crc16_modbus"
|
4
|
+
require "io/wait"
|
3
5
|
|
4
6
|
module ModBus
|
5
7
|
module RTU
|
@@ -7,26 +9,25 @@ module ModBus
|
|
7
9
|
|
8
10
|
# We have to read specific amounts of numbers of bytes from the network depending on the function code and content
|
9
11
|
def read_rtu_response(io)
|
10
|
-
|
12
|
+
# Read the slave_id and function code
|
11
13
|
msg = read(io, 2)
|
12
|
-
log logging_bytes(msg)
|
13
14
|
|
14
15
|
function_code = msg.getbyte(1)
|
15
16
|
case function_code
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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}"
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
@@ -40,38 +41,43 @@ module ModBus
|
|
40
41
|
end
|
41
42
|
|
42
43
|
def read(io, len)
|
43
|
-
result = ""
|
44
|
+
result = +""
|
44
45
|
loop do
|
45
46
|
this_iter = io.read(len - result.length)
|
46
47
|
result.concat(this_iter) if this_iter
|
47
48
|
return result if result.length == len
|
49
|
+
|
48
50
|
io.wait_readable
|
49
51
|
end
|
50
52
|
end
|
51
53
|
|
52
54
|
def read_rtu_request(io)
|
53
|
-
|
54
|
-
|
55
|
+
# Every message is a minimum of 4 bytes (slave id, function code, crc16)
|
56
|
+
msg = read(io, 4)
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
+
# If msg is nil, then our client never sent us anything and it's time to disconnect
|
59
|
+
return if msg.nil?
|
58
60
|
|
59
61
|
loop do
|
60
62
|
offset = 0
|
61
|
-
crc = msg[-2
|
63
|
+
crc = msg[-2..].unpack1("S<")
|
62
64
|
|
63
65
|
# scan the bytestream for a valid CRC
|
64
66
|
loop do
|
65
67
|
break if offset >= msg.length - 3
|
68
|
+
|
66
69
|
calculated_crc = Digest::CRC16Modbus.checksum(msg[offset..-3])
|
67
70
|
if crc == calculated_crc
|
68
71
|
is_response = (msg.getbyte(offset + 1) & 0x80 == 0x80) ||
|
69
|
-
|
70
|
-
|
71
|
-
|
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)
|
72
75
|
|
73
|
-
params = is_response
|
74
|
-
|
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
|
75
81
|
|
76
82
|
unless params.nil?
|
77
83
|
if is_response
|
@@ -82,7 +88,7 @@ module ModBus
|
|
82
88
|
@last_req_timestamp = Time.now.to_f
|
83
89
|
end
|
84
90
|
log "Server RX discarding #{offset} bytes: #{logging_bytes(msg[0...offset])}" if offset != 0
|
85
|
-
log "Server RX (#{msg.size - offset} bytes): #{logging_bytes(msg[offset
|
91
|
+
log "Server RX (#{msg.size - offset} bytes): #{logging_bytes(msg[offset..])}"
|
86
92
|
return [msg.getbyte(offset), msg.getbyte(offset + 1), params, msg[offset + 1..-3], is_response]
|
87
93
|
end
|
88
94
|
end
|
@@ -92,9 +98,9 @@ module ModBus
|
|
92
98
|
msg.concat(read(io, 1))
|
93
99
|
# maximum message size is 256, so that's as far as we have to
|
94
100
|
# be able to see at once
|
95
|
-
msg = msg[1
|
101
|
+
msg = msg[1..] if msg.length > 256
|
96
102
|
end
|
97
|
-
|
103
|
+
end
|
98
104
|
|
99
105
|
def serve(io)
|
100
106
|
loop do
|
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
|
@@ -23,15 +25,16 @@ module ModBus
|
|
23
25
|
include TCP
|
24
26
|
|
25
27
|
protected
|
28
|
+
|
26
29
|
# Open serial port
|
27
30
|
def open_connection(port_or_ipaddr, arg = nil, opts = {})
|
28
31
|
if port_or_ipaddr.is_a?(IO) || port_or_ipaddr.respond_to?(:read)
|
29
32
|
port_or_ipaddr
|
30
|
-
elsif File.exist?(port_or_ipaddr) || port_or_ipaddr.start_with?(
|
33
|
+
elsif File.exist?(port_or_ipaddr) || port_or_ipaddr.start_with?("/dev") || port_or_ipaddr.start_with?("COM")
|
31
34
|
arg ||= 9600
|
32
35
|
open_serial_port(port_or_ipaddr, arg, opts)
|
33
36
|
else
|
34
|
-
arg ||=
|
37
|
+
arg ||= 10_002
|
35
38
|
open_tcp_connection(port_or_ipaddr, arg, opts)
|
36
39
|
end
|
37
40
|
end
|
data/lib/rmodbus/rtu_server.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# RTU server implementation
|
3
5
|
# @example
|
@@ -7,7 +9,7 @@ module ModBus
|
|
7
9
|
# slave.discrete_inputs = [1,1,0,0]
|
8
10
|
# slave.holding_registers = [1,2,3,4]
|
9
11
|
# slave.input_registers = [1,2,3,4]
|
10
|
-
# srv.
|
12
|
+
# srv.logger = Logger.new($stdout)
|
11
13
|
# srv.start
|
12
14
|
class RTUServer
|
13
15
|
include Debug
|
@@ -18,13 +20,13 @@ module ModBus
|
|
18
20
|
# Init RTU server
|
19
21
|
# @param [Integer] uid slave device
|
20
22
|
# @see SP#open_serial_port
|
21
|
-
def initialize(port, baud=9600, opts = {})
|
23
|
+
def initialize(port, baud = 9600, opts = {})
|
22
24
|
Thread.abort_on_exception = true
|
23
|
-
if port.is_a?(IO) || port.respond_to?(:read)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
@sp = if port.is_a?(IO) || port.respond_to?(:read)
|
26
|
+
port
|
27
|
+
else
|
28
|
+
open_serial_port(port, baud, opts)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
|
30
32
|
# Start server
|
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
|
@@ -14,6 +16,7 @@ module ModBus
|
|
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)
|
@@ -34,13 +37,14 @@ module ModBus
|
|
34
37
|
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
35
38
|
|
36
39
|
if msg.getbyte(0) == @uid
|
37
|
-
return msg[1..-3] if msg[-2,2].
|
40
|
+
return msg[1..-3] if msg[-2, 2].unpack1("S<") == crc16(msg[0..-3])
|
41
|
+
|
38
42
|
log "Ignore package: don't match CRC"
|
39
43
|
else
|
40
44
|
log "Ignore package: don't match uid ID"
|
41
45
|
end
|
42
46
|
loop do
|
43
|
-
#waite timeout
|
47
|
+
# waite timeout
|
44
48
|
sleep(0.1)
|
45
49
|
end
|
46
50
|
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
|
@@ -13,7 +15,7 @@ module ModBus
|
|
13
15
|
# slave.discrete_inputs = [1,1,0,0]
|
14
16
|
# slave.holding_registers = [1,2,3,4]
|
15
17
|
# slave.input_registers = [1,2,3,4]
|
16
|
-
# srv.
|
18
|
+
# srv.logger = Logger.new($stdout)
|
17
19
|
# srv.start
|
18
20
|
class RTUViaTCPServer < GServer
|
19
21
|
include Debug
|
@@ -26,10 +28,10 @@ module ModBus
|
|
26
28
|
# @param [Hash] opts options of server
|
27
29
|
# @option opts [String] :host host of server default '127.0.0.1'
|
28
30
|
# @option opts [Float, Integer] :max_connection max of TCP connection with server default 4
|
29
|
-
def initialize(port =
|
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
|
-
|
34
|
+
super(port, opts[:host], opts[:max_connection])
|
35
|
+
end
|
34
36
|
end
|
35
37
|
end
|
data/lib/rmodbus/server/slave.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
2
4
|
|
3
5
|
module ModBus
|
4
6
|
module Server
|
@@ -8,7 +10,7 @@ module ModBus
|
|
8
10
|
def initialize
|
9
11
|
@coils = []
|
10
12
|
@discrete_inputs = []
|
11
|
-
@holding_registers =[]
|
13
|
+
@holding_registers = []
|
12
14
|
@input_registers = []
|
13
15
|
end
|
14
16
|
end
|
data/lib/rmodbus/server.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# Module for implementation ModBus server
|
3
5
|
module Server
|
4
|
-
autoload :Slave,
|
6
|
+
autoload :Slave, "rmodbus/server/slave"
|
5
7
|
|
6
8
|
attr_accessor :promiscuous, :request_callback, :response_callback
|
7
9
|
|
8
|
-
|
10
|
+
FUNCS = [1, 2, 3, 4, 5, 6, 15, 16, 22, 23].freeze
|
9
11
|
|
10
12
|
def with_slave(uid)
|
11
13
|
slave = slaves[uid] ||= Server::Slave.new
|
@@ -30,7 +32,7 @@ module ModBus
|
|
30
32
|
end
|
31
33
|
request_callback&.call(uid, func, params) unless is_response
|
32
34
|
|
33
|
-
if uid
|
35
|
+
if uid.zero?
|
34
36
|
slaves.each_key { |specific_uid| exec_req(specific_uid, func, params, pdu) }
|
35
37
|
return
|
36
38
|
end
|
@@ -46,9 +48,10 @@ module ModBus
|
|
46
48
|
return
|
47
49
|
end
|
48
50
|
|
49
|
-
unless
|
51
|
+
unless FUNCS.include?(func)
|
50
52
|
log("Server RX unrecognized function #{func} to #{uid}")
|
51
53
|
return unless slave
|
54
|
+
|
52
55
|
return (func | 0x80).chr + 1.chr
|
53
56
|
end
|
54
57
|
|
@@ -56,6 +59,7 @@ module ModBus
|
|
56
59
|
@pending_response_req = params
|
57
60
|
|
58
61
|
return unless slave
|
62
|
+
|
59
63
|
pdu = process_func(func, slave, pdu, params)
|
60
64
|
if response_callback
|
61
65
|
res = parse_response(pdu.getbyte(0), pdu)
|
@@ -84,80 +88,87 @@ module ModBus
|
|
84
88
|
end
|
85
89
|
|
86
90
|
def parse_response(func, res)
|
87
|
-
if func & 0x80 == 0x80 &&
|
91
|
+
if func & 0x80 == 0x80 && FUNCS.include?(func & 0x7f)
|
88
92
|
return nil unless res.length == 2
|
93
|
+
|
89
94
|
return { err: res[1].ord }
|
90
95
|
end
|
91
96
|
|
92
97
|
case func
|
93
98
|
when 1, 2
|
94
99
|
return nil unless res.length == res[1].ord + 2
|
95
|
-
|
100
|
+
|
101
|
+
res[2..].unpack_bits
|
96
102
|
when 3, 4, 23
|
97
103
|
return nil unless res.length == res[1].ord + 2
|
98
|
-
|
104
|
+
|
105
|
+
res[2..].unpack("n*")
|
99
106
|
when 5, 6, 15, 16
|
100
107
|
return nil unless res.length == 5
|
108
|
+
|
101
109
|
{}
|
102
110
|
when 22
|
103
111
|
return nil unless res.length == 7
|
112
|
+
|
104
113
|
{}
|
105
114
|
end
|
106
115
|
end
|
107
116
|
|
108
117
|
def process_func(func, slave, req, params)
|
109
118
|
case func
|
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
|
-
|
119
|
+
when 1
|
120
|
+
unless (err = validate_read_func(params, slave.coils, 2000))
|
121
|
+
val = slave.coils[params[:addr], params[:quant]].pack_bits
|
122
|
+
pdu = func.chr + val.size.chr + val
|
123
|
+
end
|
124
|
+
when 2
|
125
|
+
unless (err = validate_read_func(params, slave.discrete_inputs, 2000))
|
126
|
+
val = slave.discrete_inputs[params[:addr], params[:quant]].pack_bits
|
127
|
+
pdu = func.chr + val.size.chr + val
|
128
|
+
end
|
129
|
+
when 3
|
130
|
+
unless (err = validate_read_func(params, slave.holding_registers))
|
131
|
+
pdu = func.chr + (params[:quant] * 2).chr + slave.holding_registers[params[:addr],
|
132
|
+
params[:quant]].pack("n*")
|
133
|
+
end
|
134
|
+
when 4
|
135
|
+
unless (err = validate_read_func(params, slave.input_registers))
|
136
|
+
pdu = func.chr + (params[:quant] * 2).chr + slave.input_registers[params[:addr], params[:quant]].pack("n*")
|
137
|
+
end
|
138
|
+
when 5
|
139
|
+
unless (err = validate_write_coil_func(params, slave))
|
140
|
+
params[:val] = 1 if params[:val] == 0xff00
|
141
|
+
slave.coils[params[:addr]] = params[:val]
|
142
|
+
pdu = req
|
143
|
+
end
|
144
|
+
when 6
|
145
|
+
unless (err = validate_write_register_func(params, slave))
|
146
|
+
slave.holding_registers[params[:addr]] = params[:val]
|
147
|
+
pdu = req
|
148
|
+
end
|
149
|
+
when 15
|
150
|
+
unless (err = validate_write_multiple_coils_func(params, slave))
|
151
|
+
slave.coils[params[:addr], params[:quant]] = params[:val][0, params[:quant]]
|
152
|
+
pdu = req[0, 5]
|
153
|
+
end
|
154
|
+
when 16
|
155
|
+
unless (err = validate_write_multiple_registers_func(params, slave))
|
156
|
+
slave.holding_registers[params[:addr], params[:quant]] = params[:val]
|
157
|
+
pdu = req[0, 5]
|
158
|
+
end
|
159
|
+
when 22
|
160
|
+
unless (err = validate_write_register_func(params, slave))
|
161
|
+
addr = params[:addr]
|
162
|
+
and_mask = params[:and_mask]
|
163
|
+
slave.holding_registers[addr] = (slave.holding_registers[addr] & and_mask) | (params[:or_mask] & ~and_mask)
|
164
|
+
pdu = req
|
165
|
+
end
|
166
|
+
when 23
|
167
|
+
unless (err = validate_read_write_multiple_registers_func(params, slave))
|
168
|
+
slave.holding_registers[params[:write][:addr], params[:write][:quant]] = params[:write][:val]
|
169
|
+
pdu = func.chr + (params[:read][:quant] * 2).chr + slave.holding_registers[params[:read][:addr],
|
170
|
+
params[:read][:quant]].pack("n*")
|
171
|
+
end
|
161
172
|
end
|
162
173
|
|
163
174
|
if err
|
@@ -169,38 +180,45 @@ module ModBus
|
|
169
180
|
|
170
181
|
def parse_read_func(req, expected_length = 5)
|
171
182
|
return nil if expected_length && req.length != expected_length
|
172
|
-
|
183
|
+
|
184
|
+
{ quant: req[3, 2].unpack1("n"), addr: req[1, 2].unpack1("n") }
|
173
185
|
end
|
174
186
|
|
175
|
-
def validate_read_func(params, field, quant_max=0x7d)
|
187
|
+
def validate_read_func(params, field, quant_max = 0x7d)
|
176
188
|
return 3 unless params[:quant] <= quant_max
|
177
|
-
|
189
|
+
|
190
|
+
2 unless params[:addr] + params[:quant] <= field.size
|
178
191
|
end
|
179
192
|
|
180
193
|
def parse_write_coil_func(req)
|
181
194
|
return nil unless req.length == 5
|
182
|
-
|
195
|
+
|
196
|
+
{ addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
|
183
197
|
end
|
184
198
|
|
185
199
|
def validate_write_coil_func(params, slave)
|
186
200
|
return 2 unless params[:addr] <= slave.coils.size
|
187
|
-
|
201
|
+
|
202
|
+
3 unless params[:val].zero? || (params[:val] == 0xff00)
|
188
203
|
end
|
189
204
|
|
190
205
|
def parse_write_register_func(req)
|
191
206
|
return nil unless req.length == 5
|
192
|
-
|
207
|
+
|
208
|
+
{ addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
|
193
209
|
end
|
194
210
|
|
195
211
|
def validate_write_register_func(params, slave)
|
196
|
-
|
212
|
+
2 unless params[:addr] <= slave.holding_registers.size
|
197
213
|
end
|
198
214
|
|
199
215
|
def parse_write_multiple_coils_func(req)
|
200
216
|
return nil if req.length < 7
|
217
|
+
|
201
218
|
params = parse_read_func(req, nil)
|
202
|
-
return nil if req.length != 6 + (params[:quant] + 7) / 8
|
203
|
-
|
219
|
+
return nil if req.length != 6 + ((params[:quant] + 7) / 8)
|
220
|
+
|
221
|
+
params[:val] = req[6, params[:quant]].unpack_bits
|
204
222
|
params
|
205
223
|
end
|
206
224
|
|
@@ -210,9 +228,11 @@ module ModBus
|
|
210
228
|
|
211
229
|
def parse_write_multiple_registers_func(req)
|
212
230
|
return nil if req.length < 8
|
231
|
+
|
213
232
|
params = parse_read_func(req, nil)
|
214
|
-
return nil if req.length != 6 + params[:quant] * 2
|
215
|
-
|
233
|
+
return nil if req.length != 6 + (params[:quant] * 2)
|
234
|
+
|
235
|
+
params[:val] = req[6, params[:quant] * 2].unpack("n*")
|
216
236
|
params
|
217
237
|
end
|
218
238
|
|
@@ -222,24 +242,28 @@ module ModBus
|
|
222
242
|
|
223
243
|
def parse_mask_write_register_func(req)
|
224
244
|
return nil if req.length != 7
|
245
|
+
|
225
246
|
{
|
226
|
-
|
227
|
-
|
228
|
-
|
247
|
+
addr: req[1, 2].unpack1("n"),
|
248
|
+
and_mask: req[3, 2].unpack1("n"),
|
249
|
+
or_mask: req[5, 2].unpack1("n")
|
229
250
|
}
|
230
251
|
end
|
231
252
|
|
232
253
|
def parse_read_write_multiple_registers_func(req)
|
233
254
|
return nil if req.length < 12
|
255
|
+
|
234
256
|
params = { read: parse_read_func(req, nil),
|
235
|
-
|
257
|
+
write: parse_write_multiple_registers_func(req[4..]) }
|
236
258
|
return nil if params[:write].nil?
|
259
|
+
|
237
260
|
params
|
238
261
|
end
|
239
262
|
|
240
263
|
def validate_read_write_multiple_registers_func(params, slave)
|
241
264
|
result = validate_read_func(params[:read], slave.holding_registers)
|
242
265
|
return result if result
|
266
|
+
|
243
267
|
validate_write_multiple_registers_func(params[:write], slave)
|
244
268
|
end
|
245
269
|
end
|
data/lib/rmodbus/sp.rb
CHANGED
@@ -1,36 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
|
-
require
|
3
|
-
rescue
|
4
|
-
warn "[WARNING] Install `serialport` gem for use RTU protocols"
|
4
|
+
require "ccutrer-serialport"
|
5
|
+
rescue LoadError
|
6
|
+
warn "[WARNING] Install `ccutrer-serialport` gem for use RTU protocols"
|
5
7
|
end
|
6
8
|
|
7
9
|
module ModBus
|
8
10
|
module SP
|
9
11
|
attr_reader :port, :baud, :data_bits, :stop_bits, :parity, :read_timeout
|
12
|
+
|
10
13
|
# Open serial port
|
11
|
-
# @param [String] port name serial ports ("/dev/ttyS0"
|
14
|
+
# @param [String] port name serial ports ("/dev/ttyS0")
|
12
15
|
# @param [Integer] baud rate serial port (default 9600)
|
13
16
|
# @param [Hash] opts the options of serial port
|
14
17
|
#
|
15
18
|
# @option opts [Integer] :data_bits from 5 to 8
|
16
19
|
# @option opts [Integer] :stop_bits 1 or 2
|
17
|
-
# @option opts [Integer] :parity
|
18
|
-
# @option opts [Integer] :read_timeout default 100 ms
|
20
|
+
# @option opts [Integer] :parity :none, :even or :odd
|
19
21
|
# @return [SerialPort] io serial port
|
20
22
|
def open_serial_port(port, baud, opts = {})
|
21
23
|
@port, @baud = port, baud
|
22
24
|
|
23
|
-
@data_bits, @stop_bits, @parity
|
25
|
+
@data_bits, @stop_bits, @parity = 8, 1, :none
|
24
26
|
|
25
27
|
@data_bits = opts[:data_bits] unless opts[:data_bits].nil?
|
26
28
|
@stop_bits = opts[:stop_bits] unless opts[:stop_bits].nil?
|
27
29
|
@parity = opts[:parity] unless opts[:parity].nil?
|
28
|
-
@read_timeout = opts[:read_timeout] unless opts[:read_timeout].nil?
|
29
30
|
|
30
|
-
|
31
|
-
io.flow_control = SerialPort::NONE
|
32
|
-
io.read_timeout = @read_timeout
|
33
|
-
io
|
31
|
+
CCutrer::SerialPort.new(@port, baud: @baud, data_bits: @data_bits, stop_bits: @stop_bits, parity: @parity)
|
34
32
|
end
|
35
33
|
end
|
36
34
|
end
|
data/lib/rmodbus/tcp.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
2
4
|
|
3
5
|
module ModBus
|
4
6
|
module TCP
|
5
7
|
include Errors
|
6
8
|
attr_reader :ipaddr, :port
|
9
|
+
|
7
10
|
# Open TCP socket
|
8
11
|
#
|
9
12
|
# @param [String] ipaddr IP address of remote server
|
@@ -22,7 +25,7 @@ module ModBus
|
|
22
25
|
begin
|
23
26
|
io = Socket.tcp(@ipaddr, @port, nil, nil, connect_timeout: timeout)
|
24
27
|
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
|
25
|
-
raise ModBusTimeout.new,
|
28
|
+
raise ModBusTimeout.new, "Timed out attempting to create connection"
|
26
29
|
end
|
27
30
|
|
28
31
|
io
|
data/lib/rmodbus/tcp_client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# TCP client implementation
|
3
5
|
# @example
|
@@ -13,6 +15,7 @@ module ModBus
|
|
13
15
|
include TCP
|
14
16
|
|
15
17
|
protected
|
18
|
+
|
16
19
|
# Open TCP\IP connection
|
17
20
|
def open_connection(ipaddr, port = 502, opts = {})
|
18
21
|
open_tcp_connection(ipaddr, port, opts)
|