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/client/slave.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
# -*- coding: ascii
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "timeout"
|
2
5
|
|
3
6
|
module ModBus
|
4
7
|
class Client
|
@@ -8,15 +11,16 @@ module ModBus
|
|
8
11
|
include Options
|
9
12
|
# Number of times to retry on read and read timeouts
|
10
13
|
attr_accessor :uid
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
|
15
|
+
EXCEPTIONS = {
|
16
|
+
1 => IllegalFunction,
|
17
|
+
2 => IllegalDataAddress,
|
18
|
+
3 => IllegalDataValue,
|
19
|
+
4 => SlaveDeviceFailure,
|
20
|
+
5 => Acknowledge,
|
21
|
+
6 => SlaveDeviceBus,
|
22
|
+
8 => MemoryParityError
|
23
|
+
}.freeze
|
20
24
|
def initialize(uid, io)
|
21
25
|
@uid = uid
|
22
26
|
@io = io
|
@@ -44,7 +48,7 @@ module ModBus
|
|
44
48
|
# @param [Integer] ncoils number coils
|
45
49
|
# @return [Array] coils
|
46
50
|
def read_coils(addr, ncoils = 1)
|
47
|
-
query("\
|
51
|
+
query("\x01#{addr.to_word}#{ncoils.to_word}").unpack_bits[0..ncoils - 1]
|
48
52
|
end
|
49
53
|
|
50
54
|
def read_coil(addr)
|
@@ -60,10 +64,10 @@ module ModBus
|
|
60
64
|
# @param [Integer] val value coil (0 or other)
|
61
65
|
# @return self
|
62
66
|
def write_single_coil(addr, val)
|
63
|
-
if val
|
64
|
-
query("\
|
67
|
+
if [0, false].include?(val)
|
68
|
+
query("\x05#{addr.to_word}\x00\x00")
|
65
69
|
else
|
66
|
-
query("\
|
70
|
+
query("\x05#{addr.to_word}\xff\x00")
|
67
71
|
end
|
68
72
|
self
|
69
73
|
end
|
@@ -77,20 +81,20 @@ module ModBus
|
|
77
81
|
# @param [Integer] addr address first coil
|
78
82
|
# @param [Array] vals written coils
|
79
83
|
def write_multiple_coils(addr, vals)
|
80
|
-
nbyte = ((vals.size-1) >> 3) + 1
|
84
|
+
nbyte = ((vals.size - 1) >> 3) + 1
|
81
85
|
sum = 0
|
82
86
|
(vals.size - 1).downto(0) do |i|
|
83
|
-
sum
|
84
|
-
sum |= 1 if vals[i]
|
87
|
+
sum <<= 1
|
88
|
+
sum |= 1 if vals[i].positive?
|
85
89
|
end
|
86
90
|
|
87
|
-
s_val = ""
|
91
|
+
s_val = +""
|
88
92
|
nbyte.times do
|
89
93
|
s_val << (sum & 0xff).chr
|
90
94
|
sum >>= 8
|
91
95
|
end
|
92
96
|
|
93
|
-
query("\
|
97
|
+
query("\x0f#{addr.to_word}#{vals.size.to_word}#{nbyte.chr}#{s_val}")
|
94
98
|
self
|
95
99
|
end
|
96
100
|
alias_method :write_coils, :write_multiple_coils
|
@@ -115,7 +119,7 @@ module ModBus
|
|
115
119
|
# @param[Integer] ninputs number inputs
|
116
120
|
# @return [Array] inputs
|
117
121
|
def read_discrete_inputs(addr, ninputs = 1)
|
118
|
-
query("\
|
122
|
+
query("\x02#{addr.to_word}#{ninputs.to_word}").unpack_bits[0..ninputs - 1]
|
119
123
|
end
|
120
124
|
|
121
125
|
def read_discrete_input(addr)
|
@@ -142,7 +146,7 @@ module ModBus
|
|
142
146
|
# @param [Integer] nregs number registers
|
143
147
|
# @return [Array] registers
|
144
148
|
def read_input_registers(addr, nregs = 1)
|
145
|
-
query("\
|
149
|
+
query("\x04#{addr.to_word}#{nregs.to_word}").unpack("n*")
|
146
150
|
end
|
147
151
|
|
148
152
|
def read_input_register(addr)
|
@@ -171,7 +175,7 @@ module ModBus
|
|
171
175
|
# @param [Integer] nregs number registers
|
172
176
|
# @return [Array] registers
|
173
177
|
def read_holding_registers(addr, nregs = 1)
|
174
|
-
query("\
|
178
|
+
query("\x03#{addr.to_word}#{nregs.to_word}").unpack("n*")
|
175
179
|
end
|
176
180
|
|
177
181
|
def read_holding_register(addr)
|
@@ -187,12 +191,11 @@ module ModBus
|
|
187
191
|
# @param [Integer] val written to register
|
188
192
|
# @return self
|
189
193
|
def write_single_register(addr, val)
|
190
|
-
query("\
|
194
|
+
query("\x06#{addr.to_word}#{val.to_word}")
|
191
195
|
self
|
192
196
|
end
|
193
197
|
alias_method :write_holding_register, :write_single_register
|
194
198
|
|
195
|
-
|
196
199
|
# Write multiple holding registers
|
197
200
|
#
|
198
201
|
# @example
|
@@ -202,12 +205,9 @@ module ModBus
|
|
202
205
|
# @param [Array] val written registers
|
203
206
|
# @return self
|
204
207
|
def write_multiple_registers(addr, vals)
|
205
|
-
s_val =
|
206
|
-
vals.each do |reg|
|
207
|
-
s_val << reg.to_word
|
208
|
-
end
|
208
|
+
s_val = vals.map(&:to_word).join
|
209
209
|
|
210
|
-
query("\x10
|
210
|
+
query("\x10#{addr.to_word}#{vals.size.to_word}#{(vals.size * 2).chr}#{s_val}")
|
211
211
|
self
|
212
212
|
end
|
213
213
|
alias_method :write_holding_registers, :write_multiple_registers
|
@@ -220,7 +220,7 @@ module ModBus
|
|
220
220
|
# @param [Integer] and_mask mask for AND operation
|
221
221
|
# @param [Integer] or_mask mask for OR operation
|
222
222
|
def mask_write_register(addr, and_mask, or_mask)
|
223
|
-
query("\x16
|
223
|
+
query("\x16#{addr.to_word}#{and_mask.to_word}#{or_mask.to_word}")
|
224
224
|
self
|
225
225
|
end
|
226
226
|
|
@@ -235,16 +235,16 @@ module ModBus
|
|
235
235
|
# @param [Array] vals written registers
|
236
236
|
# @return [Array] registers
|
237
237
|
def read_write_multiple_registers(addr_r, nregs, addr_w, vals)
|
238
|
-
s_val =
|
239
|
-
vals.each do |reg|
|
240
|
-
s_val << reg.to_word
|
241
|
-
end
|
238
|
+
s_val = vals.map(&:to_word).join
|
242
239
|
|
243
|
-
query("\x17
|
244
|
-
|
240
|
+
query("\x17#{addr_r.to_word}#{nregs.to_word}#{addr_w.to_word}" \
|
241
|
+
"#{vals.size.to_word}#{(vals.size * 2).chr}#{s_val}")
|
242
|
+
.unpack("n*")
|
245
243
|
end
|
246
244
|
alias_method :read_write_holding_registers, :read_write_multiple_registers
|
247
245
|
|
246
|
+
# rubocop:disable Layout/LineLength
|
247
|
+
|
248
248
|
# Request pdu to slave device
|
249
249
|
#
|
250
250
|
# @param [String] pdu request to slave
|
@@ -266,76 +266,64 @@ module ModBus
|
|
266
266
|
begin
|
267
267
|
::Timeout.timeout(@read_retry_timeout, ModBusTimeout) do
|
268
268
|
send_pdu(request)
|
269
|
-
response = read_pdu unless uid
|
269
|
+
response = read_pdu unless uid.zero?
|
270
270
|
end
|
271
|
-
rescue ModBusTimeout
|
271
|
+
rescue ModBusTimeout
|
272
272
|
log "Timeout of read operation: (#{@read_retries - tried})"
|
273
273
|
tried += 1
|
274
274
|
retry unless tried >= @read_retries
|
275
275
|
raise ModBusTimeout.new, "Timed out during read attempt"
|
276
276
|
end
|
277
277
|
|
278
|
-
return nil if response.
|
278
|
+
return nil if response.empty?
|
279
279
|
|
280
280
|
read_func = response.getbyte(0)
|
281
281
|
if read_func >= 0x80
|
282
282
|
exc_id = response.getbyte(1)
|
283
|
-
raise
|
283
|
+
raise EXCEPTIONS[exc_id] unless EXCEPTIONS[exc_id].nil?
|
284
284
|
|
285
285
|
raise ModBusException.new, "Unknown error"
|
286
286
|
end
|
287
287
|
|
288
288
|
check_response_mismatch(request, response) if raise_exception_on_mismatch
|
289
|
-
response[2
|
289
|
+
response[2..]
|
290
290
|
end
|
291
|
+
# rubocop:enable Layout/LineLength
|
291
292
|
|
292
293
|
private
|
294
|
+
|
293
295
|
def check_response_mismatch(request, response)
|
294
296
|
read_func = response.getbyte(0)
|
295
|
-
data = response[2
|
296
|
-
#Mismatch functional code
|
297
|
+
data = response[2..]
|
298
|
+
# Mismatch functional code
|
297
299
|
send_func = request.getbyte(0)
|
298
|
-
if read_func != send_func
|
299
|
-
msg = "Function code is mismatch (expected #{send_func}, got #{read_func})"
|
300
|
-
end
|
300
|
+
msg = "Function code is mismatch (expected #{send_func}, got #{read_func})" if read_func != send_func
|
301
301
|
|
302
302
|
case read_func
|
303
|
-
when 1,2
|
304
|
-
bc = request.getword(3)/8 + 1
|
305
|
-
if data.size != bc
|
306
|
-
|
307
|
-
end
|
308
|
-
when 3,4
|
303
|
+
when 1, 2
|
304
|
+
bc = (request.getword(3) / 8) + 1
|
305
|
+
msg = "Byte count is mismatch (expected #{bc}, got #{data.size} bytes)" if data.size != bc
|
306
|
+
when 3, 4
|
309
307
|
rc = request.getword(3)
|
310
|
-
if data.size/2 != rc
|
311
|
-
|
312
|
-
end
|
313
|
-
when 5,6
|
308
|
+
msg = "Register count is mismatch (expected #{rc}, got #{data.size / 2} regs)" if data.size / 2 != rc
|
309
|
+
when 5, 6
|
314
310
|
exp_addr = request.getword(1)
|
315
311
|
got_addr = response.getword(1)
|
316
|
-
if exp_addr != got_addr
|
317
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
318
|
-
end
|
312
|
+
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})" if exp_addr != got_addr
|
319
313
|
|
320
314
|
exp_val = request.getword(3)
|
321
315
|
got_val = response.getword(3)
|
322
|
-
if exp_val != got_val
|
323
|
-
|
324
|
-
end
|
325
|
-
when 15,16
|
316
|
+
msg = "Value is mismatch (expected 0x#{exp_val.to_s(16)}, got 0x#{got_val.to_s(16)})" if exp_val != got_val
|
317
|
+
when 15, 16
|
326
318
|
exp_addr = request.getword(1)
|
327
319
|
got_addr = response.getword(1)
|
328
|
-
if exp_addr != got_addr
|
329
|
-
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
|
330
|
-
end
|
320
|
+
msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})" if exp_addr != got_addr
|
331
321
|
|
332
322
|
exp_quant = request.getword(3)
|
333
323
|
got_quant = response.getword(3)
|
334
|
-
if exp_quant != got_quant
|
335
|
-
msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})"
|
336
|
-
end
|
324
|
+
msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})" if exp_quant != got_quant
|
337
325
|
else
|
338
|
-
warn "
|
326
|
+
warn "Function (#{read_func}) is not supported raising response mismatch"
|
339
327
|
end
|
340
328
|
|
341
329
|
raise ResponseMismatch.new(msg, request, response) if msg
|
data/lib/rmodbus/client.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# @abstract
|
3
5
|
class Client
|
4
|
-
autoload :Slave,
|
6
|
+
autoload :Slave, "rmodbus/client/slave"
|
5
7
|
|
6
8
|
include Errors
|
7
9
|
include Debug
|
@@ -15,9 +17,9 @@ module ModBus
|
|
15
17
|
# @param *args depends on implementation
|
16
18
|
# @yield return client object and close it before exit
|
17
19
|
# @return [Client] client object
|
18
|
-
def initialize(*args
|
20
|
+
def initialize(*args)
|
19
21
|
# Defaults
|
20
|
-
@
|
22
|
+
@logger = nil
|
21
23
|
@raise_exception_on_mismatch = false
|
22
24
|
@read_retry_timeout = 1
|
23
25
|
@read_retries = 1
|
@@ -47,9 +49,9 @@ module ModBus
|
|
47
49
|
#
|
48
50
|
# @param [Integer, #read] uid slave devise
|
49
51
|
# @return [Slave] slave object
|
50
|
-
def with_slave(uid
|
52
|
+
def with_slave(uid)
|
51
53
|
slave = get_slave(uid, @io)
|
52
|
-
slave.
|
54
|
+
slave.logger = logger
|
53
55
|
slave.raise_exception_on_mismatch = raise_exception_on_mismatch
|
54
56
|
slave.read_retries = read_retries
|
55
57
|
slave.read_retry_timeout = read_retry_timeout
|
@@ -72,22 +74,23 @@ module ModBus
|
|
72
74
|
end
|
73
75
|
|
74
76
|
protected
|
75
|
-
|
76
|
-
|
77
|
+
|
78
|
+
def open_connection(*)
|
79
|
+
# Stub conn object
|
77
80
|
@io = Object.new
|
78
81
|
|
79
|
-
@io.instance_eval
|
82
|
+
@io.instance_eval <<~RUBY, __FILE__, __LINE__ + 1
|
80
83
|
def close
|
81
84
|
end
|
82
85
|
|
83
86
|
def closed?
|
84
87
|
true
|
85
88
|
end
|
86
|
-
|
89
|
+
RUBY
|
87
90
|
@io
|
88
91
|
end
|
89
92
|
|
90
|
-
def get_slave(uid,io)
|
93
|
+
def get_slave(uid, io)
|
91
94
|
Slave.new(uid, io)
|
92
95
|
end
|
93
96
|
end
|
data/lib/rmodbus/debug.rb
CHANGED
@@ -1,16 +1,20 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
2
4
|
|
3
5
|
module ModBus
|
4
6
|
module Debug
|
5
|
-
attr_accessor :
|
6
|
-
:read_retries,
|
7
|
-
|
7
|
+
attr_accessor :raise_exception_on_mismatch,
|
8
|
+
:read_retries,
|
9
|
+
:read_retry_timeout,
|
10
|
+
:logger
|
8
11
|
|
9
12
|
private
|
13
|
+
|
10
14
|
# Put log message on standard output
|
11
15
|
# @param [String] msg message for log
|
12
16
|
def log(msg)
|
13
|
-
|
17
|
+
logger&.debug(msg)
|
14
18
|
end
|
15
19
|
|
16
20
|
# Convert string of byte to string for log
|
@@ -19,7 +23,7 @@ module ModBus
|
|
19
23
|
# @param [String] msg input string
|
20
24
|
# @return [String] readable string of bytes
|
21
25
|
def logging_bytes(msg)
|
22
|
-
msg.
|
26
|
+
msg.unpack1("H*").gsub(/\X{2}/, "[\\0]")
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
data/lib/rmodbus/errors.rb
CHANGED
@@ -1,30 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
module Errors
|
3
|
-
class ProxyException <
|
5
|
+
class ProxyException < RuntimeError
|
4
6
|
end
|
5
7
|
|
6
|
-
class ModBusException <
|
8
|
+
class ModBusException < RuntimeError
|
7
9
|
end
|
8
10
|
|
9
11
|
class IllegalFunction < ModBusException
|
12
|
+
def initialize(msg = nil)
|
13
|
+
super(msg || "The function code received in the query is not an allowable action for the server")
|
14
|
+
end
|
10
15
|
end
|
11
16
|
|
12
17
|
class IllegalDataAddress < ModBusException
|
18
|
+
def initialize
|
19
|
+
super("The data address received in the query is not an allowable address for the server")
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
23
|
class IllegalDataValue < ModBusException
|
24
|
+
def initialize
|
25
|
+
super("A value contained in the query data field is not an allowable value for server")
|
26
|
+
end
|
16
27
|
end
|
17
28
|
|
18
29
|
class SlaveDeviceFailure < ModBusException
|
30
|
+
def initialize
|
31
|
+
super("An unrecoverable error occurred while the server was attempting to perform the requested action")
|
32
|
+
end
|
19
33
|
end
|
20
34
|
|
21
35
|
class Acknowledge < ModBusException
|
36
|
+
def initialize
|
37
|
+
super("The server has accepted the request and is processing it, but a long duration of time will be required to do so") # rubocop:disable Layout/LineLength
|
38
|
+
end
|
22
39
|
end
|
23
40
|
|
24
41
|
class SlaveDeviceBus < ModBusException
|
42
|
+
def initialize
|
43
|
+
super("The server is engaged in processing a long duration program command")
|
44
|
+
end
|
25
45
|
end
|
26
46
|
|
27
47
|
class MemoryParityError < ModBusException
|
48
|
+
def initialize
|
49
|
+
super("The extended file area failed to pass a consistency check")
|
50
|
+
end
|
28
51
|
end
|
29
52
|
|
30
53
|
class ModBusTimeout < ModBusException
|
@@ -32,6 +55,7 @@ module ModBus
|
|
32
55
|
|
33
56
|
class ResponseMismatch < ModBusException
|
34
57
|
attr_reader :request, :response
|
58
|
+
|
35
59
|
def initialize(msg, request, response)
|
36
60
|
super(msg)
|
37
61
|
@request = request
|
data/lib/rmodbus/ext.rb
CHANGED
@@ -1,85 +1,106 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
if RUBY_VERSION < "1.9"
|
4
|
-
def getbyte(index)
|
5
|
-
self[index].to_i
|
6
|
-
end
|
7
|
-
end
|
1
|
+
# frozen_string_literal: true
|
8
2
|
|
3
|
+
class String
|
4
|
+
# unpack a string of bytes into an array of integers (0 or 1)
|
5
|
+
# representing the bits in those bytes, according to how the
|
6
|
+
# ModBus protocol represents coils.
|
9
7
|
def unpack_bits
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
result = []
|
9
|
+
each_byte do |b|
|
10
|
+
8.times do
|
11
|
+
# least significant bits first within each byte
|
12
|
+
result << (b & 0x01)
|
13
|
+
b >>= 1
|
14
|
+
end
|
13
15
|
end
|
14
|
-
|
16
|
+
result
|
15
17
|
end
|
16
18
|
|
17
19
|
# Get word by index
|
18
|
-
# @param [Integer]
|
20
|
+
# @param [Integer] index index first bytes of word
|
19
21
|
# @return unpacked word
|
20
|
-
def getword(
|
21
|
-
self[
|
22
|
+
def getword(index)
|
23
|
+
self[index, 2].unpack1("n")
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
25
27
|
class Integer
|
26
|
-
|
27
|
-
# Shortcut or turning an integer into a word
|
28
|
+
# Shortcut for turning an integer into a word
|
28
29
|
def to_word
|
29
|
-
[self].pack(
|
30
|
+
[self].pack("n")
|
30
31
|
end
|
31
|
-
|
32
32
|
end
|
33
33
|
|
34
34
|
class Array
|
35
|
+
# Swap every pair of elements
|
36
|
+
def byteswap
|
37
|
+
even_elements_check
|
38
|
+
each_slice(2).flat_map(&:reverse)
|
39
|
+
end
|
40
|
+
alias_method :wordswap, :byteswap
|
35
41
|
|
36
|
-
# Given an array of
|
37
|
-
def
|
38
|
-
|
39
|
-
self.each_slice(2).map { |(lsb, msb)| [msb, lsb].pack('n*').unpack('g')[0] }
|
42
|
+
# Given an array of 16-bit unsigned integers, turn it into an array of 16-bit signed integers.
|
43
|
+
def to_16i
|
44
|
+
pack("n*").unpack("s>*")
|
40
45
|
end
|
41
46
|
|
42
|
-
# Given an array of
|
43
|
-
|
44
|
-
|
45
|
-
|
47
|
+
# Given an array of 16-bit unsigned integers, turn it into an array of 32-bit floats, halving the size.
|
48
|
+
# The pairs of 16-bit elements should be in big endian order.
|
49
|
+
def to_32f
|
50
|
+
even_elements_check
|
51
|
+
pack("n*").unpack("g*")
|
46
52
|
end
|
47
53
|
|
48
|
-
# Given an array of
|
54
|
+
# Given an array of 32-bit floats, turn it into an array of 16-bit unsigned integers, doubling the size.
|
49
55
|
def from_32f
|
50
|
-
|
56
|
+
pack("g*").unpack("n*")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Given an array of 16-bit unsigned integers, turn it into 32-bit unsigned integers, halving the size.
|
60
|
+
# The pairs of 16-bit elements should be in big endian order.
|
61
|
+
def to_32u
|
62
|
+
even_elements_check
|
63
|
+
pack("n*").unpack("N*")
|
51
64
|
end
|
52
65
|
|
53
|
-
# Given an array of
|
66
|
+
# Given an array of 16-bit unsigned integers, turn it into 32-bit signed integers, halving the size.
|
67
|
+
# The pairs of 16-bit elements should be in big endian order.
|
54
68
|
def to_32i
|
55
|
-
|
56
|
-
|
69
|
+
even_elements_check
|
70
|
+
pack("n*").unpack("l>*")
|
57
71
|
end
|
58
72
|
|
59
|
-
# Given an array of 32bit
|
60
|
-
def
|
61
|
-
|
73
|
+
# Given an array of 32bit unsigned integers, turn it into an array of 16 bit unsigned integers, doubling the size
|
74
|
+
def from_32u
|
75
|
+
pack("N*").unpack("n*")
|
62
76
|
end
|
63
77
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
78
|
+
# Given an array of 32bit signed integers, turn it into an array of 16 bit unsigned integers, doubling the size
|
79
|
+
def from_32i
|
80
|
+
pack("l>*").unpack("n*")
|
81
|
+
end
|
68
82
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
83
|
+
# pack an array of bits into a string of bytes,
|
84
|
+
# as the ModBus protocol dictates for coils
|
85
|
+
def pack_bits
|
86
|
+
# pack each slice of 8 bits per byte,
|
87
|
+
# forward order (bits 0-7 in byte 0, 8-15 in byte 1, etc.)
|
88
|
+
# non-multiples of 8 are just 0-padded
|
89
|
+
each_slice(8).map do |slice|
|
90
|
+
byte = 0
|
91
|
+
# within each byte, bit 0 is the LSB,
|
92
|
+
# and bit 7 is the MSB
|
93
|
+
slice.reverse_each do |bit|
|
94
|
+
byte <<= 1
|
95
|
+
byte |= 1 if bit.positive?
|
76
96
|
end
|
77
|
-
|
78
|
-
|
79
|
-
s << word.chr
|
80
|
-
else
|
81
|
-
s
|
82
|
-
end
|
97
|
+
byte
|
98
|
+
end.pack("C*")
|
83
99
|
end
|
84
100
|
|
101
|
+
private
|
102
|
+
|
103
|
+
def even_elements_check
|
104
|
+
raise ArgumentError, "Array requires an even number of elements: was #{size}" unless size.even?
|
105
|
+
end
|
85
106
|
end
|
data/lib/rmodbus/options.rb
CHANGED
data/lib/rmodbus/proxy.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ModBus
|
2
4
|
# Given a slave and a type of operation, execute a single or multiple read using hash syntax
|
3
5
|
class ReadOnlyProxy
|
@@ -10,9 +12,9 @@ module ModBus
|
|
10
12
|
# Note that in the case of multiples, a pluralized version of the method is sent to the slave
|
11
13
|
def [](key)
|
12
14
|
if key.instance_of?(0.class)
|
13
|
-
@slave.send("read_#{@type}", key)
|
15
|
+
@slave.send(:"read_#{@type}", key)
|
14
16
|
elsif key.instance_of?(Range)
|
15
|
-
@slave.send("read_#{@type}s", key.first, key.count)
|
17
|
+
@slave.send(:"read_#{@type}s", key.first, key.count)
|
16
18
|
else
|
17
19
|
raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
18
20
|
end
|
@@ -20,22 +22,25 @@ module ModBus
|
|
20
22
|
end
|
21
23
|
|
22
24
|
class ReadWriteProxy < ReadOnlyProxy
|
23
|
-
# Write single or multiple values to a modbus slave depending on whether a
|
24
|
-
#
|
25
|
-
#
|
25
|
+
# Write single or multiple values to a modbus slave depending on whether a
|
26
|
+
# Fixnum or a Range was given.
|
27
|
+
# Note that in the case of multiples, a pluralized version of the method is
|
28
|
+
# sent to the slave. Also when writing multiple values, the number of
|
29
|
+
# elements must match the number of registers in the range or an exception
|
30
|
+
# is raised
|
26
31
|
def []=(key, val)
|
27
32
|
if key.instance_of?(0.class)
|
28
|
-
@slave.send("write_#{@type}", key, val)
|
33
|
+
@slave.send(:"write_#{@type}", key, val)
|
29
34
|
elsif key.instance_of?(Range)
|
30
35
|
if key.count != val.size
|
31
|
-
raise ModBus::Errors::ProxyException,
|
36
|
+
raise ModBus::Errors::ProxyException,
|
37
|
+
"The size of the range must match the size of the values (#{key.count} != #{val.size})"
|
32
38
|
end
|
33
39
|
|
34
|
-
@slave.send("write_#{@type}s", key.first, val)
|
40
|
+
@slave.send(:"write_#{@type}s", key.first, val)
|
35
41
|
else
|
36
42
|
raise ModBus::Errors::ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
40
|
-
|
41
46
|
end
|