rmodbus 1.3.2 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
 - data/NEWS.md +19 -0
 - data/README.md +8 -8
 - data/examples/perfomance_rtu.rb +55 -56
 - data/examples/perfomance_rtu_via_tcp.rb +54 -55
 - data/examples/perfomance_tcp.rb +54 -55
 - data/examples/simple_xpca_gateway.rb +85 -0
 - data/examples/use_rtu_via_tcp_modbus.rb +14 -11
 - data/examples/use_tcp_modbus.rb +14 -11
 - data/lib/rmodbus/client/slave.rb +333 -0
 - data/lib/rmodbus/client.rb +15 -10
 - data/lib/rmodbus/debug.rb +12 -15
 - data/lib/rmodbus/errors.rb +26 -2
 - data/lib/rmodbus/ext.rb +72 -51
 - data/lib/rmodbus/options.rb +4 -1
 - data/lib/rmodbus/proxy.rb +14 -9
 - data/lib/rmodbus/rtu.rb +89 -125
 - data/lib/rmodbus/rtu_client.rb +22 -2
 - data/lib/rmodbus/rtu_server.rb +16 -12
 - data/lib/rmodbus/rtu_slave.rb +26 -3
 - data/lib/rmodbus/rtu_via_tcp_server.rb +12 -19
 - data/lib/rmodbus/server/slave.rb +18 -0
 - data/lib/rmodbus/server.rb +227 -84
 - data/lib/rmodbus/sp.rb +10 -12
 - data/lib/rmodbus/tcp.rb +9 -10
 - data/lib/rmodbus/tcp_client.rb +3 -0
 - data/lib/rmodbus/tcp_server.rb +41 -35
 - data/lib/rmodbus/tcp_slave.rb +19 -18
 - data/lib/rmodbus/version.rb +3 -2
 - data/lib/rmodbus.rb +20 -21
 - metadata +63 -50
 - data/Rakefile +0 -29
 - data/examples/simple-xpca-gateway.rb +0 -84
 - data/lib/rmodbus/rtu_via_tcp_client.rb +0 -26
 - data/lib/rmodbus/rtu_via_tcp_slave.rb +0 -29
 - data/lib/rmodbus/slave.rb +0 -308
 - data/spec/client_spec.rb +0 -88
 - data/spec/exception_spec.rb +0 -119
 - data/spec/ext_spec.rb +0 -52
 - data/spec/logging_spec.rb +0 -89
 - data/spec/proxy_spec.rb +0 -74
 - data/spec/read_rtu_response_spec.rb +0 -92
 - data/spec/response_mismach_spec.rb +0 -163
 - data/spec/rtu_client_spec.rb +0 -86
 - data/spec/rtu_server_spec.rb +0 -30
 - data/spec/rtu_via_tcp_client_spec.rb +0 -76
 - data/spec/rtu_via_tcp_server_spec.rb +0 -16
 - data/spec/slave_spec.rb +0 -55
 - data/spec/spec_helper.rb +0 -54
 - data/spec/tcp_client_spec.rb +0 -88
 - data/spec/tcp_server_spec.rb +0 -129
 
    
        data/lib/rmodbus/server.rb
    CHANGED
    
    | 
         @@ -1,127 +1,270 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module ModBus
         
     | 
| 
       2 
4 
     | 
    
         
             
              # Module for implementation ModBus server
         
     | 
| 
       3 
5 
     | 
    
         
             
              module Server
         
     | 
| 
       4 
     | 
    
         
            -
                 
     | 
| 
      
 6 
     | 
    
         
            +
                autoload :Slave, "rmodbus/server/slave"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                attr_accessor :promiscuous, :request_callback, :response_callback
         
     | 
| 
       5 
9 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
                 
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
                 
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 10 
     | 
    
         
            +
                FUNCS = [1, 2, 3, 4, 5, 6, 15, 16, 22, 23].freeze
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def with_slave(uid)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  slave = slaves[uid] ||= Server::Slave.new
         
     | 
| 
      
 14 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 15 
     | 
    
         
            +
                    yield slave
         
     | 
| 
      
 16 
     | 
    
         
            +
                  else
         
     | 
| 
      
 17 
     | 
    
         
            +
                    slave
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
       11 
20 
     | 
    
         | 
| 
       12 
21 
     | 
    
         
             
                private
         
     | 
| 
       13 
22 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
                def  
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
      
 23 
     | 
    
         
            +
                def slaves
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @slaves ||= {}
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def exec_req(uid, func, params, pdu, is_response: false)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  if is_response
         
     | 
| 
      
 29 
     | 
    
         
            +
                    log("Server RX response #{func & 0x7f} from #{uid}: #{params.inspect}")
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    log("Server RX function #{func} to #{uid}: #{params.inspect}")
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                  request_callback&.call(uid, func, params) unless is_response
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  if uid.zero?
         
     | 
| 
      
 36 
     | 
    
         
            +
                    slaves.each_key { |specific_uid| exec_req(specific_uid, func, params, pdu) }
         
     | 
| 
      
 37 
     | 
    
         
            +
                    return
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  slave = slaves[uid]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  return nil if !slave && !promiscuous
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  if promiscuous && !slave && is_response
         
     | 
| 
      
 43 
     | 
    
         
            +
                    # we saw a request to a slave that we don't own; try
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # and parse this as a response, not a request
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                    response_callback&.call(uid, func, params, @pending_response_req)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @pending_response_req = nil
         
     | 
| 
      
 48 
     | 
    
         
            +
                    return
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  unless FUNCS.include?(func)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    log("Server RX unrecognized function #{func} to #{uid}")
         
     | 
| 
      
 53 
     | 
    
         
            +
                    return unless slave
         
     | 
| 
       16 
54 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                    params = { :err => 1 }
         
     | 
| 
      
 55 
     | 
    
         
            +
                    return (func | 0x80).chr + 1.chr
         
     | 
| 
       19 
56 
     | 
    
         
             
                  end
         
     | 
| 
       20 
57 
     | 
    
         | 
| 
      
 58 
     | 
    
         
            +
                  # keep track of the request so that promiscuous printing of the response can have context if necessary
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @pending_response_req = params
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  return unless slave
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  pdu = process_func(func, slave, pdu, params)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  if response_callback
         
     | 
| 
      
 65 
     | 
    
         
            +
                    res = parse_response(pdu.getbyte(0), pdu)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    response_callback.call(uid, pdu.getbyte(0), res, params)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
                  pdu
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def parse_request(func, req)
         
     | 
| 
       21 
72 
     | 
    
         
             
                  case func
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       37 
     | 
    
         
            -
                        pdu = func.chr + (params[:quant] * 2).chr + holding_registers[params[:addr],params[:quant]].pack('n*')
         
     | 
| 
       38 
     | 
    
         
            -
                      end
         
     | 
| 
       39 
     | 
    
         
            -
                    when 4
         
     | 
| 
       40 
     | 
    
         
            -
                      params = parse_read_func(req, input_registers)
         
     | 
| 
       41 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       42 
     | 
    
         
            -
                        pdu = func.chr + (params[:quant] * 2).chr + input_registers[params[:addr],params[:quant]].pack('n*')
         
     | 
| 
       43 
     | 
    
         
            -
                      end
         
     | 
| 
       44 
     | 
    
         
            -
                    when 5
         
     | 
| 
       45 
     | 
    
         
            -
                      params = parse_write_coil_func(req)
         
     | 
| 
       46 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       47 
     | 
    
         
            -
                        coils[params[:addr]] = params[:val]
         
     | 
| 
       48 
     | 
    
         
            -
                        pdu = req
         
     | 
| 
       49 
     | 
    
         
            -
                      end
         
     | 
| 
       50 
     | 
    
         
            -
                    when 6
         
     | 
| 
       51 
     | 
    
         
            -
                      params = parse_write_register_func(req)
         
     | 
| 
       52 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       53 
     | 
    
         
            -
                        holding_registers[params[:addr]] = params[:val]
         
     | 
| 
       54 
     | 
    
         
            -
                        pdu = req
         
     | 
| 
       55 
     | 
    
         
            -
                      end
         
     | 
| 
       56 
     | 
    
         
            -
                    when 15
         
     | 
| 
       57 
     | 
    
         
            -
                      params = parse_write_multiple_coils_func(req)
         
     | 
| 
       58 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       59 
     | 
    
         
            -
                        coils[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
         
     | 
| 
       60 
     | 
    
         
            -
                        pdu = req[0,5]
         
     | 
| 
       61 
     | 
    
         
            -
                      end
         
     | 
| 
       62 
     | 
    
         
            -
                    when 16
         
     | 
| 
       63 
     | 
    
         
            -
                      params = parse_write_multiple_registers_func(req)
         
     | 
| 
       64 
     | 
    
         
            -
                      if params[:err] == 0
         
     | 
| 
       65 
     | 
    
         
            -
                        holding_registers[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
         
     | 
| 
       66 
     | 
    
         
            -
                        pdu = req[0,5]
         
     | 
| 
       67 
     | 
    
         
            -
                      end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  when 1, 2, 3, 4
         
     | 
| 
      
 74 
     | 
    
         
            +
                    parse_read_func(req)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  when 5
         
     | 
| 
      
 76 
     | 
    
         
            +
                    parse_write_coil_func(req)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  when 6
         
     | 
| 
      
 78 
     | 
    
         
            +
                    parse_write_register_func(req)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  when 15
         
     | 
| 
      
 80 
     | 
    
         
            +
                    parse_write_multiple_coils_func(req)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  when 16
         
     | 
| 
      
 82 
     | 
    
         
            +
                    parse_write_multiple_registers_func(req)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  when 22
         
     | 
| 
      
 84 
     | 
    
         
            +
                    parse_mask_write_register_func(req)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  when 23
         
     | 
| 
      
 86 
     | 
    
         
            +
                    parse_read_write_multiple_registers_func(req)
         
     | 
| 
       68 
87 
     | 
    
         
             
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                end
         
     | 
| 
       69 
89 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 90 
     | 
    
         
            +
                def parse_response(func, res)
         
     | 
| 
      
 91 
     | 
    
         
            +
                  if func & 0x80 == 0x80 && FUNCS.include?(func & 0x7f)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    return nil unless res.length == 2
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    return { err: res[1].ord }
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  case func
         
     | 
| 
      
 98 
     | 
    
         
            +
                  when 1, 2
         
     | 
| 
      
 99 
     | 
    
         
            +
                    return nil unless res.length == res[1].ord + 2
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                    res[2..].unpack_bits
         
     | 
| 
      
 102 
     | 
    
         
            +
                  when 3, 4, 23
         
     | 
| 
      
 103 
     | 
    
         
            +
                    return nil unless res.length == res[1].ord + 2
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                    res[2..].unpack("n*")
         
     | 
| 
      
 106 
     | 
    
         
            +
                  when 5, 6, 15, 16
         
     | 
| 
      
 107 
     | 
    
         
            +
                    return nil unless res.length == 5
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 110 
     | 
    
         
            +
                  when 22
         
     | 
| 
      
 111 
     | 
    
         
            +
                    return nil unless res.length == 7
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                    {}
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                def process_func(func, slave, req, params)
         
     | 
| 
      
 118 
     | 
    
         
            +
                  case func
         
     | 
| 
      
 119 
     | 
    
         
            +
                  when 1
         
     | 
| 
      
 120 
     | 
    
         
            +
                    unless (err = validate_read_func(params, slave.coils, 2000))
         
     | 
| 
      
 121 
     | 
    
         
            +
                      val = slave.coils[params[:addr], params[:quant]].pack_bits
         
     | 
| 
      
 122 
     | 
    
         
            +
                      pdu = func.chr + val.size.chr + val
         
     | 
| 
      
 123 
     | 
    
         
            +
                    end
         
     | 
| 
      
 124 
     | 
    
         
            +
                  when 2
         
     | 
| 
      
 125 
     | 
    
         
            +
                    unless (err = validate_read_func(params, slave.discrete_inputs, 2000))
         
     | 
| 
      
 126 
     | 
    
         
            +
                      val = slave.discrete_inputs[params[:addr], params[:quant]].pack_bits
         
     | 
| 
      
 127 
     | 
    
         
            +
                      pdu = func.chr + val.size.chr + val
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  when 3
         
     | 
| 
      
 130 
     | 
    
         
            +
                    unless (err = validate_read_func(params, slave.holding_registers))
         
     | 
| 
      
 131 
     | 
    
         
            +
                      pdu = func.chr + (params[:quant] * 2).chr + slave.holding_registers[params[:addr],
         
     | 
| 
      
 132 
     | 
    
         
            +
                                                                                          params[:quant]].pack("n*")
         
     | 
| 
      
 133 
     | 
    
         
            +
                    end
         
     | 
| 
      
 134 
     | 
    
         
            +
                  when 4
         
     | 
| 
      
 135 
     | 
    
         
            +
                    unless (err = validate_read_func(params, slave.input_registers))
         
     | 
| 
      
 136 
     | 
    
         
            +
                      pdu = func.chr + (params[:quant] * 2).chr + slave.input_registers[params[:addr], params[:quant]].pack("n*")
         
     | 
| 
      
 137 
     | 
    
         
            +
                    end
         
     | 
| 
      
 138 
     | 
    
         
            +
                  when 5
         
     | 
| 
      
 139 
     | 
    
         
            +
                    unless (err = validate_write_coil_func(params, slave))
         
     | 
| 
      
 140 
     | 
    
         
            +
                      params[:val] = 1 if params[:val] == 0xff00
         
     | 
| 
      
 141 
     | 
    
         
            +
                      slave.coils[params[:addr]] = params[:val]
         
     | 
| 
      
 142 
     | 
    
         
            +
                      pdu = req
         
     | 
| 
      
 143 
     | 
    
         
            +
                    end
         
     | 
| 
      
 144 
     | 
    
         
            +
                  when 6
         
     | 
| 
      
 145 
     | 
    
         
            +
                    unless (err = validate_write_register_func(params, slave))
         
     | 
| 
      
 146 
     | 
    
         
            +
                      slave.holding_registers[params[:addr]] = params[:val]
         
     | 
| 
      
 147 
     | 
    
         
            +
                      pdu = req
         
     | 
| 
      
 148 
     | 
    
         
            +
                    end
         
     | 
| 
      
 149 
     | 
    
         
            +
                  when 15
         
     | 
| 
      
 150 
     | 
    
         
            +
                    unless (err = validate_write_multiple_coils_func(params, slave))
         
     | 
| 
      
 151 
     | 
    
         
            +
                      slave.coils[params[:addr], params[:quant]] = params[:val][0, params[:quant]]
         
     | 
| 
      
 152 
     | 
    
         
            +
                      pdu = req[0, 5]
         
     | 
| 
      
 153 
     | 
    
         
            +
                    end
         
     | 
| 
      
 154 
     | 
    
         
            +
                  when 16
         
     | 
| 
      
 155 
     | 
    
         
            +
                    unless (err = validate_write_multiple_registers_func(params, slave))
         
     | 
| 
      
 156 
     | 
    
         
            +
                      slave.holding_registers[params[:addr], params[:quant]] = params[:val]
         
     | 
| 
      
 157 
     | 
    
         
            +
                      pdu = req[0, 5]
         
     | 
| 
      
 158 
     | 
    
         
            +
                    end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  when 22
         
     | 
| 
      
 160 
     | 
    
         
            +
                    unless (err = validate_write_register_func(params, slave))
         
     | 
| 
      
 161 
     | 
    
         
            +
                      addr = params[:addr]
         
     | 
| 
      
 162 
     | 
    
         
            +
                      and_mask = params[:and_mask]
         
     | 
| 
      
 163 
     | 
    
         
            +
                      slave.holding_registers[addr] = (slave.holding_registers[addr] & and_mask) | (params[:or_mask] & ~and_mask)
         
     | 
| 
      
 164 
     | 
    
         
            +
                      pdu = req
         
     | 
| 
      
 165 
     | 
    
         
            +
                    end
         
     | 
| 
      
 166 
     | 
    
         
            +
                  when 23
         
     | 
| 
      
 167 
     | 
    
         
            +
                    unless (err = validate_read_write_multiple_registers_func(params, slave))
         
     | 
| 
      
 168 
     | 
    
         
            +
                      slave.holding_registers[params[:write][:addr], params[:write][:quant]] = params[:write][:val]
         
     | 
| 
      
 169 
     | 
    
         
            +
                      pdu = func.chr + (params[:read][:quant] * 2).chr + slave.holding_registers[params[:read][:addr],
         
     | 
| 
      
 170 
     | 
    
         
            +
                                                                                                 params[:read][:quant]].pack("n*")
         
     | 
| 
      
 171 
     | 
    
         
            +
                    end
         
     | 
| 
      
 172 
     | 
    
         
            +
                  end
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                  if err
         
     | 
| 
      
 175 
     | 
    
         
            +
                    (func | 0x80).chr + err.chr
         
     | 
| 
       72 
176 
     | 
    
         
             
                  else
         
     | 
| 
       73 
     | 
    
         
            -
                    pdu 
     | 
| 
      
 177 
     | 
    
         
            +
                    pdu
         
     | 
| 
       74 
178 
     | 
    
         
             
                  end
         
     | 
| 
       75 
179 
     | 
    
         
             
                end
         
     | 
| 
       76 
180 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
                def parse_read_func(req,  
     | 
| 
       78 
     | 
    
         
            -
                   
     | 
| 
      
 181 
     | 
    
         
            +
                def parse_read_func(req, expected_length = 5)
         
     | 
| 
      
 182 
     | 
    
         
            +
                  return nil if expected_length && req.length != expected_length
         
     | 
| 
       79 
183 
     | 
    
         | 
| 
       80 
     | 
    
         
            -
                   
     | 
| 
      
 184 
     | 
    
         
            +
                  { quant: req[3, 2].unpack1("n"), addr: req[1, 2].unpack1("n") }
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
       81 
186 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
                  return  
     | 
| 
      
 187 
     | 
    
         
            +
                def validate_read_func(params, field, quant_max = 0x7d)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  return 3 unless params[:quant] <= quant_max
         
     | 
| 
       84 
189 
     | 
    
         | 
| 
       85 
     | 
    
         
            -
                   
     | 
| 
      
 190 
     | 
    
         
            +
                  2 unless params[:addr] + params[:quant] <= field.size
         
     | 
| 
       86 
191 
     | 
    
         
             
                end
         
     | 
| 
       87 
192 
     | 
    
         | 
| 
       88 
193 
     | 
    
         
             
                def parse_write_coil_func(req)
         
     | 
| 
       89 
     | 
    
         
            -
                   
     | 
| 
       90 
     | 
    
         
            -
                  return { :err => 2 } unless addr <= @coils.size
         
     | 
| 
      
 194 
     | 
    
         
            +
                  return nil unless req.length == 5
         
     | 
| 
       91 
195 
     | 
    
         | 
| 
       92 
     | 
    
         
            -
                   
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
      
 196 
     | 
    
         
            +
                  { addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
         
     | 
| 
      
 197 
     | 
    
         
            +
                end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                def validate_write_coil_func(params, slave)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  return 2 unless params[:addr] <= slave.coils.size
         
     | 
| 
       94 
201 
     | 
    
         | 
| 
       95 
     | 
    
         
            -
                   
     | 
| 
       96 
     | 
    
         
            -
                  return { :err => 0, :addr => addr, :val => val }
         
     | 
| 
      
 202 
     | 
    
         
            +
                  3 unless params[:val].zero? || (params[:val] == 0xff00)
         
     | 
| 
       97 
203 
     | 
    
         
             
                end
         
     | 
| 
       98 
204 
     | 
    
         | 
| 
       99 
205 
     | 
    
         
             
                def parse_write_register_func(req)
         
     | 
| 
       100 
     | 
    
         
            -
                   
     | 
| 
       101 
     | 
    
         
            -
                  return { :err => 2 } unless addr <= @holding_registers.size
         
     | 
| 
      
 206 
     | 
    
         
            +
                  return nil unless req.length == 5
         
     | 
| 
       102 
207 
     | 
    
         | 
| 
       103 
     | 
    
         
            -
                   
     | 
| 
      
 208 
     | 
    
         
            +
                  { addr: req[1, 2].unpack1("n"), val: req[3, 2].unpack1("n") }
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
       104 
210 
     | 
    
         | 
| 
       105 
     | 
    
         
            -
             
     | 
| 
       106 
     | 
    
         
            -
             
     | 
| 
      
 211 
     | 
    
         
            +
                def validate_write_register_func(params, slave)
         
     | 
| 
      
 212 
     | 
    
         
            +
                  2 unless params[:addr] <= slave.holding_registers.size
         
     | 
| 
      
 213 
     | 
    
         
            +
                end
         
     | 
| 
       107 
214 
     | 
    
         | 
| 
       108 
215 
     | 
    
         
             
                def parse_write_multiple_coils_func(req)
         
     | 
| 
       109 
     | 
    
         
            -
                   
     | 
| 
      
 216 
     | 
    
         
            +
                  return nil if req.length < 7
         
     | 
| 
       110 
217 
     | 
    
         | 
| 
       111 
     | 
    
         
            -
                   
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
      
 218 
     | 
    
         
            +
                  params = parse_read_func(req, nil)
         
     | 
| 
      
 219 
     | 
    
         
            +
                  return nil if req.length != 6 + ((params[:quant] + 7) / 8)
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                  params[:val] = req[6, params[:quant]].unpack_bits
         
     | 
| 
       114 
222 
     | 
    
         
             
                  params
         
     | 
| 
       115 
223 
     | 
    
         
             
                end
         
     | 
| 
       116 
224 
     | 
    
         | 
| 
      
 225 
     | 
    
         
            +
                def validate_write_multiple_coils_func(params, slave)
         
     | 
| 
      
 226 
     | 
    
         
            +
                  validate_read_func(params, slave.coils)
         
     | 
| 
      
 227 
     | 
    
         
            +
                end
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
       117 
229 
     | 
    
         
             
                def parse_write_multiple_registers_func(req)
         
     | 
| 
       118 
     | 
    
         
            -
                   
     | 
| 
      
 230 
     | 
    
         
            +
                  return nil if req.length < 8
         
     | 
| 
       119 
231 
     | 
    
         | 
| 
       120 
     | 
    
         
            -
                   
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
             
     | 
| 
      
 232 
     | 
    
         
            +
                  params = parse_read_func(req, nil)
         
     | 
| 
      
 233 
     | 
    
         
            +
                  return nil if req.length != 6 + (params[:quant] * 2)
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
      
 235 
     | 
    
         
            +
                  params[:val] = req[6, params[:quant] * 2].unpack("n*")
         
     | 
| 
       123 
236 
     | 
    
         
             
                  params
         
     | 
| 
       124 
237 
     | 
    
         
             
                end
         
     | 
| 
       125 
238 
     | 
    
         | 
| 
      
 239 
     | 
    
         
            +
                def validate_write_multiple_registers_func(params, slave)
         
     | 
| 
      
 240 
     | 
    
         
            +
                  validate_read_func(params, slave.holding_registers)
         
     | 
| 
      
 241 
     | 
    
         
            +
                end
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                def parse_mask_write_register_func(req)
         
     | 
| 
      
 244 
     | 
    
         
            +
                  return nil if req.length != 7
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                  {
         
     | 
| 
      
 247 
     | 
    
         
            +
                    addr: req[1, 2].unpack1("n"),
         
     | 
| 
      
 248 
     | 
    
         
            +
                    and_mask: req[3, 2].unpack1("n"),
         
     | 
| 
      
 249 
     | 
    
         
            +
                    or_mask: req[5, 2].unpack1("n")
         
     | 
| 
      
 250 
     | 
    
         
            +
                  }
         
     | 
| 
      
 251 
     | 
    
         
            +
                end
         
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                def parse_read_write_multiple_registers_func(req)
         
     | 
| 
      
 254 
     | 
    
         
            +
                  return nil if req.length < 12
         
     | 
| 
      
 255 
     | 
    
         
            +
             
     | 
| 
      
 256 
     | 
    
         
            +
                  params = { read: parse_read_func(req, nil),
         
     | 
| 
      
 257 
     | 
    
         
            +
                             write: parse_write_multiple_registers_func(req[4..]) }
         
     | 
| 
      
 258 
     | 
    
         
            +
                  return nil if params[:write].nil?
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
                  params
         
     | 
| 
      
 261 
     | 
    
         
            +
                end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                def validate_read_write_multiple_registers_func(params, slave)
         
     | 
| 
      
 264 
     | 
    
         
            +
                  result = validate_read_func(params[:read], slave.holding_registers)
         
     | 
| 
      
 265 
     | 
    
         
            +
                  return result if result
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                  validate_write_multiple_registers_func(params[:write], slave)
         
     | 
| 
      
 268 
     | 
    
         
            +
                end
         
     | 
| 
       126 
269 
     | 
    
         
             
              end
         
     | 
| 
       127 
270 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rmodbus/sp.rb
    CHANGED
    
    | 
         @@ -1,36 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            begin
         
     | 
| 
       2 
     | 
    
         
            -
              require  
     | 
| 
       3 
     | 
    
         
            -
            rescue  
     | 
| 
       4 
     | 
    
         
            -
              warn "[WARNING] Install `serialport` gem for use RTU protocols"
         
     | 
| 
      
 4 
     | 
    
         
            +
              require "ccutrer-serialport"
         
     | 
| 
      
 5 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 6 
     | 
    
         
            +
              warn "[WARNING] Install `ccutrer-serialport` gem for use RTU protocols"
         
     | 
| 
       5 
7 
     | 
    
         
             
            end
         
     | 
| 
       6 
8 
     | 
    
         | 
| 
       7 
9 
     | 
    
         
             
            module ModBus
         
     | 
| 
       8 
10 
     | 
    
         
             
              module SP
         
     | 
| 
       9 
11 
     | 
    
         
             
                attr_reader :port, :baud, :data_bits, :stop_bits, :parity, :read_timeout
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
       10 
13 
     | 
    
         
             
                # Open serial port
         
     | 
| 
       11 
     | 
    
         
            -
                # @param [String] port name serial ports ("/dev/ttyS0" 
     | 
| 
      
 14 
     | 
    
         
            +
                # @param [String] port name serial ports ("/dev/ttyS0")
         
     | 
| 
       12 
15 
     | 
    
         
             
                # @param [Integer] baud rate serial port (default 9600)
         
     | 
| 
       13 
16 
     | 
    
         
             
                # @param [Hash] opts the options of serial port
         
     | 
| 
       14 
17 
     | 
    
         
             
                #
         
     | 
| 
       15 
18 
     | 
    
         
             
                # @option opts [Integer] :data_bits from 5 to 8
         
     | 
| 
       16 
19 
     | 
    
         
             
                # @option opts [Integer] :stop_bits 1 or 2
         
     | 
| 
       17 
     | 
    
         
            -
                # @option opts [Integer] :parity  
     | 
| 
       18 
     | 
    
         
            -
                # @option opts [Integer] :read_timeout default 100 ms
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @option opts [Integer] :parity :none, :even or :odd
         
     | 
| 
       19 
21 
     | 
    
         
             
                # @return [SerialPort] io serial port
         
     | 
| 
       20 
22 
     | 
    
         
             
                def open_serial_port(port, baud, opts = {})
         
     | 
| 
       21 
23 
     | 
    
         
             
                  @port, @baud = port, baud
         
     | 
| 
       22 
24 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                  @data_bits, @stop_bits, @parity 
     | 
| 
      
 25 
     | 
    
         
            +
                  @data_bits, @stop_bits, @parity = 8, 1, :none
         
     | 
| 
       24 
26 
     | 
    
         | 
| 
       25 
27 
     | 
    
         
             
                  @data_bits = opts[:data_bits] unless opts[:data_bits].nil?
         
     | 
| 
       26 
28 
     | 
    
         
             
                  @stop_bits = opts[:stop_bits] unless opts[:stop_bits].nil?
         
     | 
| 
       27 
29 
     | 
    
         
             
                  @parity = opts[:parity] unless opts[:parity].nil?
         
     | 
| 
       28 
     | 
    
         
            -
                  @read_timeout = opts[:read_timeout] unless opts[:read_timeout].nil?
         
     | 
| 
       29 
30 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                   
     | 
| 
       31 
     | 
    
         
            -
                  io.flow_control = SerialPort::NONE
         
     | 
| 
       32 
     | 
    
         
            -
                  io.read_timeout = @read_timeout
         
     | 
| 
       33 
     | 
    
         
            -
                  io
         
     | 
| 
      
 31 
     | 
    
         
            +
                  CCutrer::SerialPort.new(@port, baud: @baud, data_bits: @data_bits, stop_bits: @stop_bits, parity: @parity)
         
     | 
| 
       34 
32 
     | 
    
         
             
                end
         
     | 
| 
       35 
33 
     | 
    
         
             
              end
         
     | 
| 
       36 
34 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rmodbus/tcp.rb
    CHANGED
    
    | 
         @@ -1,32 +1,31 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "socket"
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
5 
     | 
    
         
             
            module ModBus
         
     | 
| 
       5 
6 
     | 
    
         
             
              module TCP
         
     | 
| 
       6 
7 
     | 
    
         
             
                include Errors
         
     | 
| 
       7 
     | 
    
         
            -
                include Timeout
         
     | 
| 
       8 
8 
     | 
    
         
             
                attr_reader :ipaddr, :port
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
       9 
10 
     | 
    
         
             
                # Open TCP socket
         
     | 
| 
       10 
11 
     | 
    
         
             
                #
         
     | 
| 
       11 
12 
     | 
    
         
             
                # @param [String] ipaddr IP address of remote server
         
     | 
| 
       12 
13 
     | 
    
         
             
                # @param [Integer] port connection port
         
     | 
| 
       13 
14 
     | 
    
         
             
                # @param [Hash] opts options of connection
         
     | 
| 
       14 
15 
     | 
    
         
             
                # @option opts [Float, Integer] :connect_timeout seconds timeout for open socket
         
     | 
| 
       15 
     | 
    
         
            -
                # @return [ 
     | 
| 
      
 16 
     | 
    
         
            +
                # @return [Socket] socket
         
     | 
| 
       16 
17 
     | 
    
         
             
                #
         
     | 
| 
       17 
18 
     | 
    
         
             
                # @raise [ModBusTimeout] timed out attempting to create connection
         
     | 
| 
       18 
19 
     | 
    
         
             
                def open_tcp_connection(ipaddr, port, opts = {})
         
     | 
| 
       19 
20 
     | 
    
         
             
                  @ipaddr, @port = ipaddr, port
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                  opts[:connect_timeout] ||= 1
         
     | 
| 
      
 22 
     | 
    
         
            +
                  timeout = opts[:connect_timeout] ||= 1
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
24 
     | 
    
         
             
                  io = nil
         
     | 
| 
       24 
25 
     | 
    
         
             
                  begin
         
     | 
| 
       25 
     | 
    
         
            -
                     
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                     
     | 
| 
       28 
     | 
    
         
            -
                  rescue ModBusTimeout => err
         
     | 
| 
       29 
     | 
    
         
            -
                    raise ModBusTimeout.new, 'Timed out attempting to create connection'
         
     | 
| 
      
 26 
     | 
    
         
            +
                    io = Socket.tcp(@ipaddr, @port, nil, nil, connect_timeout: timeout)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT
         
     | 
| 
      
 28 
     | 
    
         
            +
                    raise ModBusTimeout.new, "Timed out attempting to create connection"
         
     | 
| 
       30 
29 
     | 
    
         
             
                  end
         
     | 
| 
       31 
30 
     | 
    
         | 
| 
       32 
31 
     | 
    
         
             
                  io
         
     | 
    
        data/lib/rmodbus/tcp_client.rb
    CHANGED
    
    | 
         @@ -1,3 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module ModBus
         
     | 
| 
       2 
4 
     | 
    
         
             
              # TCP client implementation
         
     | 
| 
       3 
5 
     | 
    
         
             
              # @example
         
     | 
| 
         @@ -13,6 +15,7 @@ module ModBus 
     | 
|
| 
       13 
15 
     | 
    
         
             
                include TCP
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
       15 
17 
     | 
    
         
             
                protected
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       16 
19 
     | 
    
         
             
                # Open TCP\IP connection
         
     | 
| 
       17 
20 
     | 
    
         
             
                def open_connection(ipaddr, port = 502, opts = {})
         
     | 
| 
       18 
21 
     | 
    
         
             
                  open_tcp_connection(ipaddr, port, opts)
         
     | 
    
        data/lib/rmodbus/tcp_server.rb
    CHANGED
    
    | 
         @@ -1,5 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            begin
         
     | 
| 
       2 
     | 
    
         
            -
              require  
     | 
| 
      
 4 
     | 
    
         
            +
              require "gserver"
         
     | 
| 
       3 
5 
     | 
    
         
             
            rescue
         
     | 
| 
       4 
6 
     | 
    
         
             
              warn "[WARNING] Install `gserver` gem for use TCPServer"
         
     | 
| 
       5 
7 
     | 
    
         
             
            end
         
     | 
| 
         @@ -7,16 +9,17 @@ end 
     | 
|
| 
       7 
9 
     | 
    
         
             
            module ModBus
         
     | 
| 
       8 
10 
     | 
    
         
             
              # TCP server implementation
         
     | 
| 
       9 
11 
     | 
    
         
             
              # @example
         
     | 
| 
       10 
     | 
    
         
            -
              #   srv = TCPServer.new(10002 
     | 
| 
       11 
     | 
    
         
            -
              #    
     | 
| 
       12 
     | 
    
         
            -
              #    
     | 
| 
       13 
     | 
    
         
            -
              #    
     | 
| 
       14 
     | 
    
         
            -
              #    
     | 
| 
       15 
     | 
    
         
            -
              #    
     | 
| 
      
 12 
     | 
    
         
            +
              #   srv = TCPServer.new(10002)
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   slave = srv.with_slave(255)
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   slave.coils = [1,0,1,1]
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   slave.discrete_inputs = [1,1,0,0]
         
     | 
| 
      
 16 
     | 
    
         
            +
              #   slave.holding_registers = [1,2,3,4]
         
     | 
| 
      
 17 
     | 
    
         
            +
              #   slave.input_registers = [1,2,3,4]
         
     | 
| 
      
 18 
     | 
    
         
            +
              #   srv.logger = Logger.new($stdout)
         
     | 
| 
       16 
19 
     | 
    
         
             
              #   srv.start
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
              class TCPServer < GServer
         
     | 
| 
      
 21 
     | 
    
         
            +
                include Debug
         
     | 
| 
      
 22 
     | 
    
         
            +
                include Server
         
     | 
| 
       20 
23 
     | 
    
         | 
| 
       21 
24 
     | 
    
         
             
                # Init server
         
     | 
| 
       22 
25 
     | 
    
         
             
                # @param [Integer] port listen port
         
     | 
| 
         @@ -24,40 +27,43 @@ module ModBus 
     | 
|
| 
       24 
27 
     | 
    
         
             
                # @param [Hash] opts options of server
         
     | 
| 
       25 
28 
     | 
    
         
             
                # @option opts [String] :host host of server default '127.0.0.1'
         
     | 
| 
       26 
29 
     | 
    
         
             
                # @option opts [Float, Integer] :max_connection max of TCP connection with server default 4
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
            			@uid = uid
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                  warn "[WARNING] Please, use UID = 255. It will be fixed in the next release." if @uid != 0xff
         
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
      
 30 
     | 
    
         
            +
                def initialize(port = 502, opts = {})
         
     | 
| 
       32 
31 
     | 
    
         
             
                  opts[:host] = DEFAULT_HOST unless opts[:host]
         
     | 
| 
       33 
32 
     | 
    
         
             
                  opts[:max_connection] = 4 unless opts[:max_connection]
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 33 
     | 
    
         
            +
                  super(port, opts[:host], opts[:max_connection])
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                # set the default param
         
     | 
| 
      
 37 
     | 
    
         
            +
                def with_slave(uid = 255)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  super
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
       36 
40 
     | 
    
         | 
| 
       37 
41 
     | 
    
         
             
                # Serve requests
         
     | 
| 
       38 
42 
     | 
    
         
             
                # @param [TCPSocket] io socket
         
     | 
| 
       39 
43 
     | 
    
         
             
                def serve(io)
         
     | 
| 
       40 
     | 
    
         
            -
                   
     | 
| 
      
 44 
     | 
    
         
            +
                  until stopped?
         
     | 
| 
       41 
45 
     | 
    
         
             
                    header = io.read(7)
         
     | 
| 
       42 
     | 
    
         
            -
                    tx_id = header[0,2]
         
     | 
| 
       43 
     | 
    
         
            -
                    proto_id = header[2,2]
         
     | 
| 
       44 
     | 
    
         
            -
                    len = header[4,2]. 
     | 
| 
      
 46 
     | 
    
         
            +
                    tx_id = header[0, 2]
         
     | 
| 
      
 47 
     | 
    
         
            +
                    proto_id = header[2, 2]
         
     | 
| 
      
 48 
     | 
    
         
            +
                    len = header[4, 2].unpack1("n")
         
     | 
| 
       45 
49 
     | 
    
         
             
                    unit_id = header.getbyte(6)
         
     | 
| 
       46 
     | 
    
         
            -
                     
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
                       
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                       
     | 
| 
      
 50 
     | 
    
         
            +
                    next unless proto_id == "\x00\x00"
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    req = io.read(len - 1)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    log "Server RX (#{req.size} bytes): #{logging_bytes(req)}"
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    func = req.getbyte(0)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    params = parse_request(func, req)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    pdu = exec_req(unit_id, func, params, req)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    if pdu
         
     | 
| 
      
 60 
     | 
    
         
            +
                      resp = "#{tx_id}\x00\x00#{(pdu.size + 1).to_word}#{unit_id.chr}#{pdu}"
         
     | 
| 
      
 61 
     | 
    
         
            +
                      log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
         
     | 
| 
      
 62 
     | 
    
         
            +
                      io.write resp
         
     | 
| 
      
 63 
     | 
    
         
            +
                    else
         
     | 
| 
      
 64 
     | 
    
         
            +
                      log "Ignored server RX (invalid unit ID #{unit_id}, #{req.size} bytes): #{logging_bytes(req)}"
         
     | 
| 
       59 
65 
     | 
    
         
             
                    end
         
     | 
| 
       60 
66 
     | 
    
         
             
                  end
         
     | 
| 
       61 
67 
     | 
    
         
             
                end
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
       63 
69 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rmodbus/tcp_slave.rb
    CHANGED
    
    | 
         @@ -1,3 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            module ModBus
         
     | 
| 
       2 
4 
     | 
    
         
             
              # TCP slave implementation
         
     | 
| 
       3 
5 
     | 
    
         
             
              # @example
         
     | 
| 
         @@ -7,25 +9,26 @@ module ModBus 
     | 
|
| 
       7 
9 
     | 
    
         
             
              #     end
         
     | 
| 
       8 
10 
     | 
    
         
             
              #   end
         
     | 
| 
       9 
11 
     | 
    
         
             
              #
         
     | 
| 
       10 
     | 
    
         
            -
              # @see  
     | 
| 
      
 12 
     | 
    
         
            +
              # @see TCP#open_tcp_connection
         
     | 
| 
       11 
13 
     | 
    
         
             
              # @see Client#with_slave
         
     | 
| 
       12 
14 
     | 
    
         
             
              # @see Slave
         
     | 
| 
       13 
     | 
    
         
            -
              class TCPSlave < Slave
         
     | 
| 
      
 15 
     | 
    
         
            +
              class TCPSlave < Client::Slave
         
     | 
| 
       14 
16 
     | 
    
         
             
                attr_reader :transaction
         
     | 
| 
       15 
17 
     | 
    
         | 
| 
       16 
18 
     | 
    
         
             
                # @see Slave::initialize
         
     | 
| 
       17 
19 
     | 
    
         
             
                def initialize(uid, io)
         
     | 
| 
       18 
20 
     | 
    
         
             
                  @transaction = 0
         
     | 
| 
       19 
     | 
    
         
            -
                  super 
     | 
| 
      
 21 
     | 
    
         
            +
                  super
         
     | 
| 
       20 
22 
     | 
    
         
             
                end
         
     | 
| 
       21 
23 
     | 
    
         | 
| 
       22 
24 
     | 
    
         
             
                private
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
       23 
26 
     | 
    
         
             
                # overide method for RTU over TCP implamentaion
         
     | 
| 
       24 
27 
     | 
    
         
             
                # @see Slave#query
         
     | 
| 
       25 
28 
     | 
    
         
             
                def send_pdu(pdu)
         
     | 
| 
       26 
     | 
    
         
            -
                  @transaction = 0 if @transaction.next >  
     | 
| 
      
 29 
     | 
    
         
            +
                  @transaction = 0 if @transaction.next > 65_535
         
     | 
| 
       27 
30 
     | 
    
         
             
                  @transaction += 1
         
     | 
| 
       28 
     | 
    
         
            -
                  msg = @transaction.to_word 
     | 
| 
      
 31 
     | 
    
         
            +
                  msg = "#{@transaction.to_word}\x00\x00#{(pdu.size + 1).to_word}#{@uid.chr}#{pdu}"
         
     | 
| 
       29 
32 
     | 
    
         
             
                  @io.write msg
         
     | 
| 
       30 
33 
     | 
    
         | 
| 
       31 
34 
     | 
    
         
             
                  log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
         
     | 
| 
         @@ -36,19 +39,17 @@ module ModBus 
     | 
|
| 
       36 
39 
     | 
    
         
             
                def read_pdu
         
     | 
| 
       37 
40 
     | 
    
         
             
                  loop do
         
     | 
| 
       38 
41 
     | 
    
         
             
                    header = @io.read(7)
         
     | 
| 
       39 
     | 
    
         
            -
                     
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                      end
         
     | 
| 
       51 
     | 
    
         
            -
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                    next unless header
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    trn = header[0, 2].unpack1("n")
         
     | 
| 
      
 45 
     | 
    
         
            +
                    len = header[4, 2].unpack1("n")
         
     | 
| 
      
 46 
     | 
    
         
            +
                    msg = @io.read(len - 1)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    log "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg)
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    return msg if trn == @transaction
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    log "Transaction number mismatch. A packet is ignored."
         
     | 
| 
       52 
53 
     | 
    
         
             
                  end
         
     | 
| 
       53 
54 
     | 
    
         
             
                end
         
     | 
| 
       54 
55 
     | 
    
         
             
              end
         
     |