rmodbus-ccutrer 2.0.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|