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/slave.rb
DELETED
@@ -1,308 +0,0 @@
|
|
1
|
-
module ModBus
|
2
|
-
class Slave
|
3
|
-
include Errors
|
4
|
-
include Debug
|
5
|
-
include Options
|
6
|
-
# Number of times to retry on read and read timeouts
|
7
|
-
attr_accessor :uid
|
8
|
-
Exceptions = {
|
9
|
-
1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
|
10
|
-
2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
|
11
|
-
3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
|
12
|
-
4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
|
13
|
-
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"),
|
14
|
-
6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
|
15
|
-
8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
|
16
|
-
}
|
17
|
-
def initialize(uid, io)
|
18
|
-
@uid = uid
|
19
|
-
@io = io
|
20
|
-
end
|
21
|
-
|
22
|
-
# Returns a ModBus::ReadWriteProxy hash interface for coils
|
23
|
-
#
|
24
|
-
# @example
|
25
|
-
# coils[addr] => [1]
|
26
|
-
# coils[addr1..addr2] => [1, 0, ..]
|
27
|
-
# coils[addr] = 0 => [0]
|
28
|
-
# coils[addr1..addr2] = [1, 0, ..] => [1, 0, ..]
|
29
|
-
#
|
30
|
-
# @return [ReadWriteProxy] proxy object
|
31
|
-
def coils
|
32
|
-
ModBus::ReadWriteProxy.new(self, :coil)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Read coils
|
36
|
-
#
|
37
|
-
# @example
|
38
|
-
# read_coils(addr, ncoils) => [1, 0, ..]
|
39
|
-
#
|
40
|
-
# @param [Integer] addr address first coil
|
41
|
-
# @param [Integer] ncoils number coils
|
42
|
-
# @return [Array] coils
|
43
|
-
def read_coils(addr, ncoils)
|
44
|
-
query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
|
45
|
-
end
|
46
|
-
alias_method :read_coil, :read_coils
|
47
|
-
|
48
|
-
# Write a single coil
|
49
|
-
#
|
50
|
-
# @example
|
51
|
-
# write_single_coil(1, 0) => self
|
52
|
-
#
|
53
|
-
# @param [Integer] addr address coil
|
54
|
-
# @param [Integer] val value coil (0 or other)
|
55
|
-
# @return self
|
56
|
-
def write_single_coil(addr, val)
|
57
|
-
if val == 0
|
58
|
-
query("\x5" + addr.to_word + 0.to_word)
|
59
|
-
else
|
60
|
-
query("\x5" + addr.to_word + 0xff00.to_word)
|
61
|
-
end
|
62
|
-
self
|
63
|
-
end
|
64
|
-
alias_method :write_coil, :write_single_coil
|
65
|
-
|
66
|
-
# Write multiple coils
|
67
|
-
#
|
68
|
-
# @example
|
69
|
-
# write_multiple_coils(1, [0,1,0,1]) => self
|
70
|
-
#
|
71
|
-
# @param [Integer] addr address first coil
|
72
|
-
# @param [Array] vals written coils
|
73
|
-
def write_multiple_coils(addr, vals)
|
74
|
-
nbyte = ((vals.size-1) >> 3) + 1
|
75
|
-
sum = 0
|
76
|
-
(vals.size - 1).downto(0) do |i|
|
77
|
-
sum = sum << 1
|
78
|
-
sum |= 1 if vals[i] > 0
|
79
|
-
end
|
80
|
-
|
81
|
-
s_val = ""
|
82
|
-
nbyte.times do
|
83
|
-
s_val << (sum & 0xff).chr
|
84
|
-
sum >>= 8
|
85
|
-
end
|
86
|
-
|
87
|
-
query("\xf" + addr.to_word + vals.size.to_word + nbyte.chr + s_val)
|
88
|
-
self
|
89
|
-
end
|
90
|
-
alias_method :write_coils, :write_multiple_coils
|
91
|
-
|
92
|
-
# Returns a ModBus::ReadOnlyProxy hash interface for discrete inputs
|
93
|
-
#
|
94
|
-
# @example
|
95
|
-
# discrete_inputs[addr] => [1]
|
96
|
-
# discrete_inputs[addr1..addr2] => [1, 0, ..]
|
97
|
-
#
|
98
|
-
# @return [ReadOnlyProxy] proxy object
|
99
|
-
def discrete_inputs
|
100
|
-
ModBus::ReadOnlyProxy.new(self, :discrete_input)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Read discrete inputs
|
104
|
-
#
|
105
|
-
# @example
|
106
|
-
# read_discrete_inputs(addr, ninputs) => [1, 0, ..]
|
107
|
-
#
|
108
|
-
# @param [Integer] addr address first input
|
109
|
-
# @param[Integer] ninputs number inputs
|
110
|
-
# @return [Array] inputs
|
111
|
-
def read_discrete_inputs(addr, ninputs)
|
112
|
-
query("\x2" + addr.to_word + ninputs.to_word).unpack_bits[0..ninputs-1]
|
113
|
-
end
|
114
|
-
alias_method :read_discrete_input, :read_discrete_inputs
|
115
|
-
|
116
|
-
# Returns a read/write ModBus::ReadOnlyProxy hash interface for coils
|
117
|
-
#
|
118
|
-
# @example
|
119
|
-
# input_registers[addr] => [1]
|
120
|
-
# input_registers[addr1..addr2] => [1, 0, ..]
|
121
|
-
#
|
122
|
-
# @return [ReadOnlyProxy] proxy object
|
123
|
-
def input_registers
|
124
|
-
ModBus::ReadOnlyProxy.new(self, :input_register)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Read input registers
|
128
|
-
#
|
129
|
-
# @example
|
130
|
-
# read_input_registers(1, 5) => [1, 0, ..]
|
131
|
-
#
|
132
|
-
# @param [Integer] addr address first registers
|
133
|
-
# @param [Integer] nregs number registers
|
134
|
-
# @return [Array] registers
|
135
|
-
def read_input_registers(addr, nregs)
|
136
|
-
query("\x4" + addr.to_word + nregs.to_word).unpack('n*')
|
137
|
-
end
|
138
|
-
alias_method :read_input_register, :read_input_registers
|
139
|
-
|
140
|
-
# Returns a ModBus::ReadWriteProxy hash interface for holding registers
|
141
|
-
#
|
142
|
-
# @example
|
143
|
-
# holding_registers[addr] => [123]
|
144
|
-
# holding_registers[addr1..addr2] => [123, 234, ..]
|
145
|
-
# holding_registers[addr] = 123 => 123
|
146
|
-
# holding_registers[addr1..addr2] = [234, 345, ..] => [234, 345, ..]
|
147
|
-
#
|
148
|
-
# @return [ReadWriteProxy] proxy object
|
149
|
-
def holding_registers
|
150
|
-
ModBus::ReadWriteProxy.new(self, :holding_register)
|
151
|
-
end
|
152
|
-
|
153
|
-
# Read holding registers
|
154
|
-
#
|
155
|
-
# @example
|
156
|
-
# read_holding_registers(1, 5) => [1, 0, ..]
|
157
|
-
#
|
158
|
-
# @param [Integer] addr address first registers
|
159
|
-
# @param [Integer] nregs number registers
|
160
|
-
# @return [Array] registers
|
161
|
-
def read_holding_registers(addr, nregs)
|
162
|
-
query("\x3" + addr.to_word + nregs.to_word).unpack('n*')
|
163
|
-
end
|
164
|
-
alias_method :read_holding_register, :read_holding_registers
|
165
|
-
|
166
|
-
# Write a single holding register
|
167
|
-
#
|
168
|
-
# @example
|
169
|
-
# write_single_register(1, 0xaa) => self
|
170
|
-
#
|
171
|
-
# @param [Integer] addr address registers
|
172
|
-
# @param [Integer] val written to register
|
173
|
-
# @return self
|
174
|
-
def write_single_register(addr, val)
|
175
|
-
query("\x6" + addr.to_word + val.to_word)
|
176
|
-
self
|
177
|
-
end
|
178
|
-
alias_method :write_holding_register, :write_single_register
|
179
|
-
|
180
|
-
|
181
|
-
# Write multiple holding registers
|
182
|
-
#
|
183
|
-
# @example
|
184
|
-
# write_multiple_registers(1, [0xaa, 0]) => self
|
185
|
-
#
|
186
|
-
# @param [Integer] addr address first registers
|
187
|
-
# @param [Array] val written registers
|
188
|
-
# @return self
|
189
|
-
def write_multiple_registers(addr, vals)
|
190
|
-
s_val = ""
|
191
|
-
vals.each do |reg|
|
192
|
-
s_val << reg.to_word
|
193
|
-
end
|
194
|
-
|
195
|
-
query("\x10" + addr.to_word + vals.size.to_word + (vals.size * 2).chr + s_val)
|
196
|
-
self
|
197
|
-
end
|
198
|
-
alias_method :write_holding_registers, :write_multiple_registers
|
199
|
-
|
200
|
-
# Mask a holding register
|
201
|
-
#
|
202
|
-
# @example
|
203
|
-
# mask_write_register(1, 0xAAAA, 0x00FF) => self
|
204
|
-
# @param [Integer] addr address registers
|
205
|
-
# @param [Integer] and_mask mask for AND operation
|
206
|
-
# @param [Integer] or_mask mask for OR operation
|
207
|
-
def mask_write_register(addr, and_mask, or_mask)
|
208
|
-
query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
|
209
|
-
self
|
210
|
-
end
|
211
|
-
|
212
|
-
# Request pdu to slave device
|
213
|
-
#
|
214
|
-
# @param [String] pdu request to slave
|
215
|
-
# @return [String] received data
|
216
|
-
#
|
217
|
-
# @raise [ResponseMismatch] the received echo response differs from the request
|
218
|
-
# @raise [ModBusTimeout] timed out during read attempt
|
219
|
-
# @raise [ModBusException] unknown error
|
220
|
-
# @raise [IllegalFunction] function code received in the query is not an allowable action for the server
|
221
|
-
# @raise [IllegalDataAddress] data address received in the query is not an allowable address for the server
|
222
|
-
# @raise [IllegalDataValue] value contained in the query data field is not an allowable value for server
|
223
|
-
# @raise [SlaveDeviceFailure] unrecoverable error occurred while the server was attempting to perform the requested action
|
224
|
-
# @raise [Acknowledge] server has accepted the request and is processing it, but a long duration of time will be required to do so
|
225
|
-
# @raise [SlaveDeviceBus] server is engaged in processing a long duration program command
|
226
|
-
# @raise [MemoryParityError] extended file area failed to pass a consistency check
|
227
|
-
def query(request)
|
228
|
-
tried = 0
|
229
|
-
response = ""
|
230
|
-
begin
|
231
|
-
::Timeout.timeout(@read_retry_timeout, ModBusTimeout) do
|
232
|
-
send_pdu(request)
|
233
|
-
response = read_pdu
|
234
|
-
end
|
235
|
-
rescue ModBusTimeout => err
|
236
|
-
log "Timeout of read operation: (#{@read_retries - tried})"
|
237
|
-
tried += 1
|
238
|
-
retry unless tried >= @read_retries
|
239
|
-
raise ModBusTimeout.new, "Timed out during read attempt"
|
240
|
-
end
|
241
|
-
|
242
|
-
return nil if response.size == 0
|
243
|
-
|
244
|
-
read_func = response.getbyte(0)
|
245
|
-
if read_func >= 0x80
|
246
|
-
exc_id = response.getbyte(1)
|
247
|
-
raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
|
248
|
-
|
249
|
-
raise ModBusException.new, "Unknown error"
|
250
|
-
end
|
251
|
-
|
252
|
-
check_response_mismatch(request, response) if raise_exception_on_mismatch
|
253
|
-
response[2..-1]
|
254
|
-
end
|
255
|
-
|
256
|
-
private
|
257
|
-
def check_response_mismatch(request, response)
|
258
|
-
read_func = response.getbyte(0)
|
259
|
-
data = response[2..-1]
|
260
|
-
#Mismatch functional code
|
261
|
-
send_func = request.getbyte(0)
|
262
|
-
if read_func != send_func
|
263
|
-
msg = "Function code is mismatch (expected #{send_func}, got #{read_func})"
|
264
|
-
end
|
265
|
-
|
266
|
-
case read_func
|
267
|
-
when 1,2
|
268
|
-
bc = request.getword(3)/8 + 1
|
269
|
-
if data.size != bc
|
270
|
-
msg = "Byte count is mismatch (expected #{bc}, got #{data.size} bytes)"
|
271
|
-
end
|
272
|
-
when 3,4
|
273
|
-
rc = request.getword(3)
|
274
|
-
if data.size/2 != rc
|
275
|
-
msg = "Register count is mismatch (expected #{rc}, got #{data.size/2} regs)"
|
276
|
-
end
|
277
|
-
when 5,6
|
278
|
-
exp_addr = request.getword(1)
|
279
|
-
got_addr = response.getword(1)
|
280
|
-
if exp_addr != got_addr
|
281
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
282
|
-
end
|
283
|
-
|
284
|
-
exp_val = request.getword(3)
|
285
|
-
got_val = response.getword(3)
|
286
|
-
if exp_val != got_val
|
287
|
-
msg = "Value is mismatch (expected 0x#{exp_val.to_s(16)}, got 0x#{got_val.to_s(16)})"
|
288
|
-
end
|
289
|
-
when 15,16
|
290
|
-
exp_addr = request.getword(1)
|
291
|
-
got_addr = response.getword(1)
|
292
|
-
if exp_addr != got_addr
|
293
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
294
|
-
end
|
295
|
-
|
296
|
-
exp_quant = request.getword(3)
|
297
|
-
got_quant = response.getword(3)
|
298
|
-
if exp_quant != got_quant
|
299
|
-
msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})"
|
300
|
-
end
|
301
|
-
else
|
302
|
-
warn "Fuiction (#{read_func}) is not supported raising response mismatch"
|
303
|
-
end
|
304
|
-
|
305
|
-
raise ResponseMismatch.new(msg, request, response) if msg
|
306
|
-
end
|
307
|
-
end
|
308
|
-
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
|