rmodbus-ccutrer 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/NEWS.md +14 -7
- data/README.md +8 -8
- data/examples/perfomance_rtu.rb +55 -56
- data/examples/perfomance_rtu_via_tcp.rb +54 -55
- data/examples/perfomance_tcp.rb +54 -55
- data/examples/simple_xpca_gateway.rb +85 -0
- data/examples/use_rtu_via_tcp_modbus.rb +14 -11
- data/examples/use_tcp_modbus.rb +14 -11
- data/lib/rmodbus/client/slave.rb +58 -70
- data/lib/rmodbus/client.rb +13 -10
- data/lib/rmodbus/debug.rb +10 -6
- data/lib/rmodbus/errors.rb +26 -2
- data/lib/rmodbus/ext.rb +72 -51
- data/lib/rmodbus/options.rb +4 -1
- data/lib/rmodbus/proxy.rb +14 -9
- data/lib/rmodbus/rtu.rb +38 -32
- data/lib/rmodbus/rtu_client.rb +5 -2
- data/lib/rmodbus/rtu_server.rb +9 -7
- data/lib/rmodbus/rtu_slave.rb +6 -2
- data/lib/rmodbus/rtu_via_tcp_server.rb +7 -5
- data/lib/rmodbus/server/slave.rb +4 -2
- data/lib/rmodbus/server.rb +97 -73
- data/lib/rmodbus/sp.rb +10 -12
- data/lib/rmodbus/tcp.rb +5 -2
- data/lib/rmodbus/tcp_client.rb +3 -0
- data/lib/rmodbus/tcp_server.rb +28 -26
- data/lib/rmodbus/tcp_slave.rb +17 -16
- data/lib/rmodbus/version.rb +3 -1
- data/lib/rmodbus.rb +20 -18
- metadata +50 -49
- data/Rakefile +0 -29
- data/examples/simple-xpca-gateway.rb +0 -84
- data/spec/client_spec.rb +0 -88
- data/spec/exception_spec.rb +0 -120
- data/spec/ext_spec.rb +0 -52
- data/spec/logging_spec.rb +0 -89
- data/spec/proxy_spec.rb +0 -74
- data/spec/read_rtu_response_spec.rb +0 -92
- data/spec/response_mismach_spec.rb +0 -163
- data/spec/rtu_client_spec.rb +0 -86
- data/spec/rtu_server_spec.rb +0 -31
- data/spec/rtu_via_tcp_client_spec.rb +0 -76
- data/spec/rtu_via_tcp_server_spec.rb +0 -89
- data/spec/slave_spec.rb +0 -55
- data/spec/spec_helper.rb +0 -54
- data/spec/tcp_client_spec.rb +0 -88
- data/spec/tcp_server_spec.rb +0 -158
data/lib/rmodbus/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
|