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/slave.rb
DELETED
@@ -1,310 +0,0 @@
|
|
1
|
-
require 'timeout'
|
2
|
-
|
3
|
-
module ModBus
|
4
|
-
class Slave
|
5
|
-
include Errors
|
6
|
-
include Debug
|
7
|
-
include Options
|
8
|
-
# Number of times to retry on read and read timeouts
|
9
|
-
attr_accessor :uid
|
10
|
-
Exceptions = {
|
11
|
-
1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
|
12
|
-
2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
|
13
|
-
3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
|
14
|
-
4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
|
15
|
-
5 => Acknowledge.new("The server has accepted the request and is processing it, but a long duration of time will be required to do so"),
|
16
|
-
6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
|
17
|
-
8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
|
18
|
-
}
|
19
|
-
def initialize(uid, io)
|
20
|
-
@uid = uid
|
21
|
-
@io = io
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns a ModBus::ReadWriteProxy hash interface for coils
|
25
|
-
#
|
26
|
-
# @example
|
27
|
-
# coils[addr] => [1]
|
28
|
-
# coils[addr1..addr2] => [1, 0, ..]
|
29
|
-
# coils[addr] = 0 => [0]
|
30
|
-
# coils[addr1..addr2] = [1, 0, ..] => [1, 0, ..]
|
31
|
-
#
|
32
|
-
# @return [ReadWriteProxy] proxy object
|
33
|
-
def coils
|
34
|
-
ModBus::ReadWriteProxy.new(self, :coil)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Read coils
|
38
|
-
#
|
39
|
-
# @example
|
40
|
-
# read_coils(addr, ncoils) => [1, 0, ..]
|
41
|
-
#
|
42
|
-
# @param [Integer] addr address first coil
|
43
|
-
# @param [Integer] ncoils number coils
|
44
|
-
# @return [Array] coils
|
45
|
-
def read_coils(addr, ncoils)
|
46
|
-
query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
|
47
|
-
end
|
48
|
-
alias_method :read_coil, :read_coils
|
49
|
-
|
50
|
-
# Write a single coil
|
51
|
-
#
|
52
|
-
# @example
|
53
|
-
# write_single_coil(1, 0) => self
|
54
|
-
#
|
55
|
-
# @param [Integer] addr address coil
|
56
|
-
# @param [Integer] val value coil (0 or other)
|
57
|
-
# @return self
|
58
|
-
def write_single_coil(addr, val)
|
59
|
-
if val == 0
|
60
|
-
query("\x5" + addr.to_word + 0.to_word)
|
61
|
-
else
|
62
|
-
query("\x5" + addr.to_word + 0xff00.to_word)
|
63
|
-
end
|
64
|
-
self
|
65
|
-
end
|
66
|
-
alias_method :write_coil, :write_single_coil
|
67
|
-
|
68
|
-
# Write multiple coils
|
69
|
-
#
|
70
|
-
# @example
|
71
|
-
# write_multiple_coils(1, [0,1,0,1]) => self
|
72
|
-
#
|
73
|
-
# @param [Integer] addr address first coil
|
74
|
-
# @param [Array] vals written coils
|
75
|
-
def write_multiple_coils(addr, vals)
|
76
|
-
nbyte = ((vals.size-1) >> 3) + 1
|
77
|
-
sum = 0
|
78
|
-
(vals.size - 1).downto(0) do |i|
|
79
|
-
sum = sum << 1
|
80
|
-
sum |= 1 if vals[i] > 0
|
81
|
-
end
|
82
|
-
|
83
|
-
s_val = ""
|
84
|
-
nbyte.times do
|
85
|
-
s_val << (sum & 0xff).chr
|
86
|
-
sum >>= 8
|
87
|
-
end
|
88
|
-
|
89
|
-
query("\xf" + addr.to_word + vals.size.to_word + nbyte.chr + s_val)
|
90
|
-
self
|
91
|
-
end
|
92
|
-
alias_method :write_coils, :write_multiple_coils
|
93
|
-
|
94
|
-
# Returns a ModBus::ReadOnlyProxy hash interface for discrete inputs
|
95
|
-
#
|
96
|
-
# @example
|
97
|
-
# discrete_inputs[addr] => [1]
|
98
|
-
# discrete_inputs[addr1..addr2] => [1, 0, ..]
|
99
|
-
#
|
100
|
-
# @return [ReadOnlyProxy] proxy object
|
101
|
-
def discrete_inputs
|
102
|
-
ModBus::ReadOnlyProxy.new(self, :discrete_input)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Read discrete inputs
|
106
|
-
#
|
107
|
-
# @example
|
108
|
-
# read_discrete_inputs(addr, ninputs) => [1, 0, ..]
|
109
|
-
#
|
110
|
-
# @param [Integer] addr address first input
|
111
|
-
# @param[Integer] ninputs number inputs
|
112
|
-
# @return [Array] inputs
|
113
|
-
def read_discrete_inputs(addr, ninputs)
|
114
|
-
query("\x2" + addr.to_word + ninputs.to_word).unpack_bits[0..ninputs-1]
|
115
|
-
end
|
116
|
-
alias_method :read_discrete_input, :read_discrete_inputs
|
117
|
-
|
118
|
-
# Returns a read/write ModBus::ReadOnlyProxy hash interface for coils
|
119
|
-
#
|
120
|
-
# @example
|
121
|
-
# input_registers[addr] => [1]
|
122
|
-
# input_registers[addr1..addr2] => [1, 0, ..]
|
123
|
-
#
|
124
|
-
# @return [ReadOnlyProxy] proxy object
|
125
|
-
def input_registers
|
126
|
-
ModBus::ReadOnlyProxy.new(self, :input_register)
|
127
|
-
end
|
128
|
-
|
129
|
-
# Read input registers
|
130
|
-
#
|
131
|
-
# @example
|
132
|
-
# read_input_registers(1, 5) => [1, 0, ..]
|
133
|
-
#
|
134
|
-
# @param [Integer] addr address first registers
|
135
|
-
# @param [Integer] nregs number registers
|
136
|
-
# @return [Array] registers
|
137
|
-
def read_input_registers(addr, nregs)
|
138
|
-
query("\x4" + addr.to_word + nregs.to_word).unpack('n*')
|
139
|
-
end
|
140
|
-
alias_method :read_input_register, :read_input_registers
|
141
|
-
|
142
|
-
# Returns a ModBus::ReadWriteProxy hash interface for holding registers
|
143
|
-
#
|
144
|
-
# @example
|
145
|
-
# holding_registers[addr] => [123]
|
146
|
-
# holding_registers[addr1..addr2] => [123, 234, ..]
|
147
|
-
# holding_registers[addr] = 123 => 123
|
148
|
-
# holding_registers[addr1..addr2] = [234, 345, ..] => [234, 345, ..]
|
149
|
-
#
|
150
|
-
# @return [ReadWriteProxy] proxy object
|
151
|
-
def holding_registers
|
152
|
-
ModBus::ReadWriteProxy.new(self, :holding_register)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Read holding registers
|
156
|
-
#
|
157
|
-
# @example
|
158
|
-
# read_holding_registers(1, 5) => [1, 0, ..]
|
159
|
-
#
|
160
|
-
# @param [Integer] addr address first registers
|
161
|
-
# @param [Integer] nregs number registers
|
162
|
-
# @return [Array] registers
|
163
|
-
def read_holding_registers(addr, nregs)
|
164
|
-
query("\x3" + addr.to_word + nregs.to_word).unpack('n*')
|
165
|
-
end
|
166
|
-
alias_method :read_holding_register, :read_holding_registers
|
167
|
-
|
168
|
-
# Write a single holding register
|
169
|
-
#
|
170
|
-
# @example
|
171
|
-
# write_single_register(1, 0xaa) => self
|
172
|
-
#
|
173
|
-
# @param [Integer] addr address registers
|
174
|
-
# @param [Integer] val written to register
|
175
|
-
# @return self
|
176
|
-
def write_single_register(addr, val)
|
177
|
-
query("\x6" + addr.to_word + val.to_word)
|
178
|
-
self
|
179
|
-
end
|
180
|
-
alias_method :write_holding_register, :write_single_register
|
181
|
-
|
182
|
-
|
183
|
-
# Write multiple holding registers
|
184
|
-
#
|
185
|
-
# @example
|
186
|
-
# write_multiple_registers(1, [0xaa, 0]) => self
|
187
|
-
#
|
188
|
-
# @param [Integer] addr address first registers
|
189
|
-
# @param [Array] val written registers
|
190
|
-
# @return self
|
191
|
-
def write_multiple_registers(addr, vals)
|
192
|
-
s_val = ""
|
193
|
-
vals.each do |reg|
|
194
|
-
s_val << reg.to_word
|
195
|
-
end
|
196
|
-
|
197
|
-
query("\x10" + addr.to_word + vals.size.to_word + (vals.size * 2).chr + s_val)
|
198
|
-
self
|
199
|
-
end
|
200
|
-
alias_method :write_holding_registers, :write_multiple_registers
|
201
|
-
|
202
|
-
# Mask a holding register
|
203
|
-
#
|
204
|
-
# @example
|
205
|
-
# mask_write_register(1, 0xAAAA, 0x00FF) => self
|
206
|
-
# @param [Integer] addr address registers
|
207
|
-
# @param [Integer] and_mask mask for AND operation
|
208
|
-
# @param [Integer] or_mask mask for OR operation
|
209
|
-
def mask_write_register(addr, and_mask, or_mask)
|
210
|
-
query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
|
211
|
-
self
|
212
|
-
end
|
213
|
-
|
214
|
-
# Request pdu to slave device
|
215
|
-
#
|
216
|
-
# @param [String] pdu request to slave
|
217
|
-
# @return [String] received data
|
218
|
-
#
|
219
|
-
# @raise [ResponseMismatch] the received echo response differs from the request
|
220
|
-
# @raise [ModBusTimeout] timed out during read attempt
|
221
|
-
# @raise [ModBusException] unknown error
|
222
|
-
# @raise [IllegalFunction] function code received in the query is not an allowable action for the server
|
223
|
-
# @raise [IllegalDataAddress] data address received in the query is not an allowable address for the server
|
224
|
-
# @raise [IllegalDataValue] value contained in the query data field is not an allowable value for server
|
225
|
-
# @raise [SlaveDeviceFailure] unrecoverable error occurred while the server was attempting to perform the requested action
|
226
|
-
# @raise [Acknowledge] server has accepted the request and is processing it, but a long duration of time will be required to do so
|
227
|
-
# @raise [SlaveDeviceBus] server is engaged in processing a long duration program command
|
228
|
-
# @raise [MemoryParityError] extended file area failed to pass a consistency check
|
229
|
-
def query(request)
|
230
|
-
tried = 0
|
231
|
-
response = ""
|
232
|
-
begin
|
233
|
-
::Timeout.timeout(@read_retry_timeout, ModBusTimeout) do
|
234
|
-
send_pdu(request)
|
235
|
-
response = read_pdu
|
236
|
-
end
|
237
|
-
rescue ModBusTimeout => err
|
238
|
-
log "Timeout of read operation: (#{@read_retries - tried})"
|
239
|
-
tried += 1
|
240
|
-
retry unless tried >= @read_retries
|
241
|
-
raise ModBusTimeout.new, "Timed out during read attempt"
|
242
|
-
end
|
243
|
-
|
244
|
-
return nil if response.size == 0
|
245
|
-
|
246
|
-
read_func = response.getbyte(0)
|
247
|
-
if read_func >= 0x80
|
248
|
-
exc_id = response.getbyte(1)
|
249
|
-
raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
|
250
|
-
|
251
|
-
raise ModBusException.new, "Unknown error"
|
252
|
-
end
|
253
|
-
|
254
|
-
check_response_mismatch(request, response) if raise_exception_on_mismatch
|
255
|
-
response[2..-1]
|
256
|
-
end
|
257
|
-
|
258
|
-
private
|
259
|
-
def check_response_mismatch(request, response)
|
260
|
-
read_func = response.getbyte(0)
|
261
|
-
data = response[2..-1]
|
262
|
-
#Mismatch functional code
|
263
|
-
send_func = request.getbyte(0)
|
264
|
-
if read_func != send_func
|
265
|
-
msg = "Function code is mismatch (expected #{send_func}, got #{read_func})"
|
266
|
-
end
|
267
|
-
|
268
|
-
case read_func
|
269
|
-
when 1,2
|
270
|
-
bc = request.getword(3)/8 + 1
|
271
|
-
if data.size != bc
|
272
|
-
msg = "Byte count is mismatch (expected #{bc}, got #{data.size} bytes)"
|
273
|
-
end
|
274
|
-
when 3,4
|
275
|
-
rc = request.getword(3)
|
276
|
-
if data.size/2 != rc
|
277
|
-
msg = "Register count is mismatch (expected #{rc}, got #{data.size/2} regs)"
|
278
|
-
end
|
279
|
-
when 5,6
|
280
|
-
exp_addr = request.getword(1)
|
281
|
-
got_addr = response.getword(1)
|
282
|
-
if exp_addr != got_addr
|
283
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
284
|
-
end
|
285
|
-
|
286
|
-
exp_val = request.getword(3)
|
287
|
-
got_val = response.getword(3)
|
288
|
-
if exp_val != got_val
|
289
|
-
msg = "Value is mismatch (expected 0x#{exp_val.to_s(16)}, got 0x#{got_val.to_s(16)})"
|
290
|
-
end
|
291
|
-
when 15,16
|
292
|
-
exp_addr = request.getword(1)
|
293
|
-
got_addr = response.getword(1)
|
294
|
-
if exp_addr != got_addr
|
295
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
296
|
-
end
|
297
|
-
|
298
|
-
exp_quant = request.getword(3)
|
299
|
-
got_quant = response.getword(3)
|
300
|
-
if exp_quant != got_quant
|
301
|
-
msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})"
|
302
|
-
end
|
303
|
-
else
|
304
|
-
warn "Fuiction (#{read_func}) is not supported raising response mismatch"
|
305
|
-
end
|
306
|
-
|
307
|
-
raise ResponseMismatch.new(msg, request, response) if msg
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
data/spec/client_spec.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
# -*- coding: ascii
|
2
|
-
require 'rmodbus'
|
3
|
-
|
4
|
-
describe ModBus::Client do
|
5
|
-
before do
|
6
|
-
@cl = ModBus::Client.new
|
7
|
-
end
|
8
|
-
|
9
|
-
it "should give object provider for slave" do
|
10
|
-
slave = @cl.with_slave(1)
|
11
|
-
slave.uid.should eq(1)
|
12
|
-
end
|
13
|
-
|
14
|
-
it "should give object provider for slave in block" do
|
15
|
-
@cl.with_slave(1) do |slave|
|
16
|
-
slave.uid.should eq(1)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should connect with TCP server" do
|
21
|
-
ModBus::Client.connect do |cl|
|
22
|
-
cl.should be_instance_of(ModBus::Client)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
it ":new alias :connect" do
|
27
|
-
ModBus::Client.new do |cl|
|
28
|
-
cl.should be_instance_of(ModBus::Client)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should close the connection when an exception is raised in the given block" do
|
33
|
-
expect {
|
34
|
-
ModBus::Client.new do |client|
|
35
|
-
client.should_receive(:close)
|
36
|
-
raise
|
37
|
-
end
|
38
|
-
}.to raise_error
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'should common for all slaves :debug flag' do
|
42
|
-
@cl.debug = true
|
43
|
-
@cl.with_slave(1) do |slave_1|
|
44
|
-
slave_1.debug.should be_truthy
|
45
|
-
end
|
46
|
-
@cl.with_slave(2) do |slave_2|
|
47
|
-
slave_2.debug = false
|
48
|
-
slave_2.debug.should be_falsey
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'should common for all slaves :raise_exception_on_mismatch flag' do
|
53
|
-
@cl.raise_exception_on_mismatch = true
|
54
|
-
@cl.with_slave(1) do |slave_1|
|
55
|
-
slave_1.raise_exception_on_mismatch.should be_truthy
|
56
|
-
end
|
57
|
-
|
58
|
-
@cl.with_slave(2) do |slave_2|
|
59
|
-
slave_2.raise_exception_on_mismatch = false
|
60
|
-
slave_2.raise_exception_on_mismatch.should be_falsey
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'should common for all slaves :read_retries options' do
|
65
|
-
@cl.read_retries = 5
|
66
|
-
@cl.with_slave(1) do |slave_1|
|
67
|
-
slave_1.read_retries.should eql(5)
|
68
|
-
end
|
69
|
-
|
70
|
-
@cl.with_slave(2) do |slave_2|
|
71
|
-
slave_2.read_retries = 15
|
72
|
-
slave_2.read_retries.should eql(15)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'should common for all slaves :read_retry_timeout options' do
|
77
|
-
@cl.read_retry_timeout = 5
|
78
|
-
@cl.with_slave(1) do |slave_1|
|
79
|
-
slave_1.read_retry_timeout.should eql(5)
|
80
|
-
end
|
81
|
-
|
82
|
-
@cl.with_slave(2) do |slave_2|
|
83
|
-
slave_2.read_retry_timeout = 15
|
84
|
-
slave_2.read_retry_timeout.should eql(15)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|
data/spec/exception_spec.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
# -*- coding: ascii
|
2
|
-
require 'rmodbus'
|
3
|
-
|
4
|
-
describe ModBus::TCPClient do
|
5
|
-
before(:all) do
|
6
|
-
@srv = ModBus::TCPServer.new(1502, 1)
|
7
|
-
@srv.coils = [0] * 8
|
8
|
-
@srv.discrete_inputs = [0] * 8
|
9
|
-
@srv.holding_registers = [0] * 8
|
10
|
-
@srv.input_registers = [0] * 8
|
11
|
-
@srv.start
|
12
|
-
|
13
|
-
@cl = ModBus::TCPClient.new('127.0.0.1', 1502)
|
14
|
-
@slave = @cl.with_slave(1)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should raise ProxException" do
|
18
|
-
lambda { @slave.holding_registers[0..2] = [0,0] }.should raise_error(ModBus::Errors::ProxyException)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Read coil status
|
22
|
-
it "should read coil status" do
|
23
|
-
@slave.read_coils(0, 4).should == [0] * 4
|
24
|
-
end
|
25
|
-
|
26
|
-
it "should raise exception if illegal data address" do
|
27
|
-
lambda { @slave.read_coils(501, 34) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should raise exception if too many data" do
|
31
|
-
lambda { @slave.read_coils(0, 0x07D1) }.should raise_error(ModBus::Errors::IllegalDataValue)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Read input status
|
35
|
-
it "should read discrete inputs" do
|
36
|
-
@slave.read_discrete_inputs(0, 4).should == [0] * 4
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should raise exception if illegal data address" do
|
40
|
-
lambda { @slave.read_discrete_inputs(50, 23) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should raise exception if too many data" do
|
44
|
-
lambda { @slave.read_discrete_inputs(0, 0x07D1) }.should raise_error(ModBus::Errors::IllegalDataValue)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Read holding registers
|
48
|
-
it "should read discrete inputs" do
|
49
|
-
@slave.read_holding_registers(0, 4).should == [0, 0, 0, 0]
|
50
|
-
end
|
51
|
-
|
52
|
-
it "should raise exception if illegal data address" do
|
53
|
-
lambda { @slave.read_holding_registers(402, 99) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
it "should raise exception if too many data" do
|
58
|
-
lambda { @slave.read_holding_registers(0, 0x007E) }.should raise_error(ModBus::Errors::IllegalDataValue)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Read input registers
|
62
|
-
it "should read discrete inputs" do
|
63
|
-
@slave.read_input_registers(0, 4).should == [0, 0, 0, 0]
|
64
|
-
end
|
65
|
-
|
66
|
-
it "should raise exception if illegal data address" do
|
67
|
-
lambda { @slave.read_input_registers(402, 9) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
68
|
-
end
|
69
|
-
|
70
|
-
it "should raise exception if too many data" do
|
71
|
-
lambda { @slave.read_input_registers(0, 0x007E) }.should raise_error(ModBus::Errors::IllegalDataValue)
|
72
|
-
end
|
73
|
-
|
74
|
-
# Force single coil
|
75
|
-
it "should force single coil" do
|
76
|
-
@slave.write_single_coil(4, 1).should == @slave
|
77
|
-
@slave.read_coils(4, 4).should == [1, 0, 0, 0]
|
78
|
-
end
|
79
|
-
|
80
|
-
it "should raise exception if illegal data address" do
|
81
|
-
lambda { @slave.write_single_coil(501, true) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Preset single register
|
85
|
-
it "should preset single register" do
|
86
|
-
@slave.write_single_register(4, 0x0AA0).should == @slave
|
87
|
-
@slave.read_holding_registers(4, 1).should == [0x0AA0]
|
88
|
-
end
|
89
|
-
|
90
|
-
it "should raise exception if illegal data address" do
|
91
|
-
lambda { @slave.write_single_register(501, 0x0AA0) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
92
|
-
end
|
93
|
-
|
94
|
-
# Force multiple coils
|
95
|
-
it "should force multiple coils" do
|
96
|
-
@slave.write_multiple_coils(4, [0,1,0,1]).should == @slave
|
97
|
-
@slave.read_coils(3, 5).should == [0,0,1,0,1]
|
98
|
-
end
|
99
|
-
|
100
|
-
it "should raise exception if illegal data address" do
|
101
|
-
lambda { @slave.write_multiple_coils(501, [1,0]) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Preset multiple registers
|
105
|
-
it "should preset multiple registers" do
|
106
|
-
@slave.write_multiple_registers(4, [1, 2, 3, 0xAACC]).should == @slave
|
107
|
-
@slave.read_holding_registers(3, 5).should == [0, 1, 2, 3, 0xAACC]
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should raise exception if illegal data address" do
|
111
|
-
lambda { @slave.write_multiple_registers(501, [1, 2]) }.should raise_error(ModBus::Errors::IllegalDataAddress)
|
112
|
-
end
|
113
|
-
|
114
|
-
after(:all) do
|
115
|
-
@cl.close unless @cl.closed?
|
116
|
-
@srv.stop
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
data/spec/ext_spec.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
# -*- coding: ascii
|
2
|
-
require 'rmodbus'
|
3
|
-
|
4
|
-
describe Array do
|
5
|
-
before do
|
6
|
-
@arr = [1,0,1,1, 0,0,1,1, 1,1,0,1, 0,1,1,0, 1,0,1]
|
7
|
-
@test = [0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0]
|
8
|
-
end
|
9
|
-
|
10
|
-
it "should return string reprisent 16bit" do
|
11
|
-
@arr.pack_to_word.should == "\xcd\x6b\x5"
|
12
|
-
end
|
13
|
-
|
14
|
-
it "fixed bug for divisible 8 data " do
|
15
|
-
([0] * 8).pack_to_word.should == "\x00"
|
16
|
-
end
|
17
|
-
|
18
|
-
it "should unpack to @test" do
|
19
|
-
"test".unpack_bits == @test
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should turn an array into 32b ints" do
|
23
|
-
[20342, 17344].to_32i.should == [1136676726]
|
24
|
-
[20342, 17344, 20342, 17344].to_32i.size.should == 2
|
25
|
-
end
|
26
|
-
|
27
|
-
it "should turn an array into 32b floats big endian" do
|
28
|
-
[20342, 17344].to_32f[0].should be_within(0.1).of(384.620788574219)
|
29
|
-
[20342, 17344, 20342, 17344].to_32f.size.should == 2
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should turn a an array into 32b floats (little endian)" do
|
33
|
-
[17344, 20342].to_32f_le[0].should be_within(0.1).of(384.620788574219)
|
34
|
-
[17344, 20342, 17344, 20342].to_32f_le.size.should == 2
|
35
|
-
end
|
36
|
-
|
37
|
-
it "should turn an array from 32b ints into 16b ints, big endian" do
|
38
|
-
[1136676726].from_32i.should == [20342, 17344]
|
39
|
-
[1136676726, 1136676725].from_32i.should == [20342, 17344, 20341, 17344]
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should turn an array from 32b floats into 16b ints, big endian" do
|
43
|
-
[384.620788].from_32f.should == [20342, 17344]
|
44
|
-
[384.620788, 384.620788].from_32f.should == [20342, 17344, 20342, 17344]
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should raise exception if uneven number of elements" do
|
48
|
-
lambda { [20342, 17344, 123].to_32f }.should raise_error(StandardError)
|
49
|
-
lambda { [20342, 17344, 123].to_32i }.should raise_error(StandardError)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
data/spec/logging_spec.rb
DELETED
@@ -1,89 +0,0 @@
|
|
1
|
-
# -*- coding: ascii
|
2
|
-
require 'rmodbus'
|
3
|
-
|
4
|
-
describe ModBus::TCPClient do
|
5
|
-
before(:each) do
|
6
|
-
@uid = 1
|
7
|
-
@sock = double('Socket')
|
8
|
-
@adu = "\000\001\000\000\000\001\001"
|
9
|
-
|
10
|
-
TCPSocket.should_receive(:new).with('127.0.0.1', 1502).and_return(@sock)
|
11
|
-
@sock.stub(:read).with(0).and_return('')
|
12
|
-
|
13
|
-
@slave = ModBus::TCPClient.new('127.0.0.1', 1502).with_slave(@uid)
|
14
|
-
@slave.debug = true
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'should log rec\send bytes' do
|
18
|
-
request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
19
|
-
mock_query(request,response)
|
20
|
-
$stdout.should_receive(:puts).with("Tx (12 bytes): [00][01][00][00][00][06][01][03][00][6b][00][03]")
|
21
|
-
$stdout.should_receive(:puts).with("Rx (15 bytes): [00][01][00][00][00][09][01][03][06][02][2b][00][00][00][64]")
|
22
|
-
@slave.query(request)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should don't logging if debug disable" do
|
26
|
-
@slave.debug = false
|
27
|
-
request, response = "\x3\x0\x6b\x0\x3", "\x3\x6\x2\x2b\x0\x0\x0\x64"
|
28
|
-
mock_query(request,response)
|
29
|
-
@slave.query(request)
|
30
|
-
end
|
31
|
-
|
32
|
-
it "should log warn message if transaction mismatch" do
|
33
|
-
@adu[0,2] = @slave.transaction.next.to_word
|
34
|
-
@sock.should_receive(:write).with(@adu)
|
35
|
-
@sock.should_receive(:read).with(7).and_return("\000\002\000\000\000\001" + @uid.chr)
|
36
|
-
@sock.should_receive(:read).with(7).and_return("\000\001\000\000\000\001" + @uid.chr)
|
37
|
-
|
38
|
-
$stdout.should_receive(:puts).with("Tx (7 bytes): [00][01][00][00][00][01][01]")
|
39
|
-
$stdout.should_receive(:puts).with("Rx (7 bytes): [00][02][00][00][00][01][01]")
|
40
|
-
$stdout.should_receive(:puts).with("Transaction number mismatch. A packet is ignored.")
|
41
|
-
$stdout.should_receive(:puts).with("Rx (7 bytes): [00][01][00][00][00][01][01]")
|
42
|
-
|
43
|
-
@slave.query('')
|
44
|
-
end
|
45
|
-
|
46
|
-
def mock_query(request, response)
|
47
|
-
@adu = @slave.transaction.next.to_word + "\x0\x0\x0\x9" + @uid.chr + request
|
48
|
-
@sock.should_receive(:write).with(@adu[0,4] + "\0\6" + @uid.chr + request)
|
49
|
-
@sock.should_receive(:read).with(7).and_return(@adu[0,7])
|
50
|
-
@sock.should_receive(:read).with(8).and_return(response)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
begin
|
55
|
-
require "serialport"
|
56
|
-
describe ModBus::RTUClient do
|
57
|
-
before do
|
58
|
-
@sp = double('Serial port')
|
59
|
-
|
60
|
-
SerialPort.should_receive(:new).with("/dev/port1", 9600, 7, 2, SerialPort::ODD).and_return(@sp)
|
61
|
-
SerialPort.stub(:public_method_defined?).with(:flush_input).and_return(true)
|
62
|
-
|
63
|
-
@sp.stub(:class).and_return(SerialPort)
|
64
|
-
@sp.should_receive(:flow_control=).with(SerialPort::NONE)
|
65
|
-
@sp.stub(:read_timeout=)
|
66
|
-
@sp.stub(:flush_input)
|
67
|
-
|
68
|
-
@slave = ModBus::RTUClient.new("/dev/port1", 9600, :data_bits => 7, :stop_bits => 2, :parity => SerialPort::ODD).with_slave(1)
|
69
|
-
@slave.read_retries = 0
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'should log rec\send bytes' do
|
74
|
-
request = "\x3\x0\x1\x0\x1"
|
75
|
-
@sp.should_receive(:write).with("\1#{request}\xd5\xca")
|
76
|
-
@sp.should_receive(:flush_input) # Clean a garbage
|
77
|
-
@sp.should_receive(:read).with(2).and_return("\x1\x3")
|
78
|
-
@sp.should_receive(:read).with(1).and_return("\x2")
|
79
|
-
@sp.should_receive(:read).with(4).and_return("\xff\xff\xb9\xf4")
|
80
|
-
|
81
|
-
@slave.debug = true
|
82
|
-
$stdout.should_receive(:puts).with("Tx (8 bytes): [01][03][00][01][00][01][d5][ca]")
|
83
|
-
$stdout.should_receive(:puts).with("Rx (7 bytes): [01][03][02][ff][ff][b9][f4]")
|
84
|
-
|
85
|
-
@slave.query(request).should == "\xff\xff"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
rescue LoadError
|
89
|
-
end
|