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
         
     |