Module: ModBus::Server

Included in:
RTUServer, RTUViaTCPServer, TCPServer
Defined in:
lib/rmodbus/server.rb,
lib/rmodbus/server/slave.rb

Overview

Module for implementation ModBus server

Defined Under Namespace

Classes: Slave

Constant Summary collapse

Funcs =
[1,2,3,4,5,6,15,16,22,23]

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#promiscuousObject

Returns the value of attribute promiscuous.



6
7
8
# File 'lib/rmodbus/server.rb', line 6

def promiscuous
  @promiscuous
end

#request_callbackObject

Returns the value of attribute request_callback.



6
7
8
# File 'lib/rmodbus/server.rb', line 6

def request_callback
  @request_callback
end

#response_callbackObject

Returns the value of attribute response_callback.



6
7
8
# File 'lib/rmodbus/server.rb', line 6

def response_callback
  @response_callback
end

Instance Method Details

#exec_req(uid, func, params, pdu, is_response: false) ⇒ Object (private)



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/rmodbus/server.rb', line 25

def exec_req(uid, func, params, pdu, is_response: false)
  if is_response
    log("Server RX response #{func & 0x7f} from #{uid}: #{params.inspect}")
  else
    log("Server RX function #{func} to #{uid}: #{params.inspect}")
  end
  request_callback&.call(uid, func, params) unless is_response

  if uid == 0
    slaves.each_key { |specific_uid| exec_req(specific_uid, func, params, pdu) }
    return
  end
  slave = slaves[uid]
  return nil if !slave && !promiscuous

  if promiscuous && !slave && is_response
    # we saw a request to a slave that we don't own; try
    # and parse this as a response, not a request

    response_callback&.call(uid, func, params, @pending_response_req)
    @pending_response_req = nil
    return
  end

  unless Funcs.include?(func)
    log("Server RX unrecognized function #{func} to #{uid}")
    return unless slave
    return (func | 0x80).chr + 1.chr
  end

  # keep track of the request so that promiscuous printing of the response can have context if necessary
  @pending_response_req = params

  return unless slave
  pdu = process_func(func, slave, pdu, params)
  if response_callback
    res = parse_response(pdu.getbyte(0), pdu)
    response_callback.call(uid, pdu.getbyte(0), res, params)
  end
  pdu
end

#parse_mask_write_register_func(req) ⇒ Object (private)



223
224
225
226
227
228
229
230
# File 'lib/rmodbus/server.rb', line 223

def parse_mask_write_register_func(req)
  return nil if req.length != 7
  {
      addr: req[1,2].unpack('n')[0],
      and_mask: req[3,2].unpack('n')[0],
      or_mask: req[5,2].unpack('n')[0]
  }
end

#parse_read_func(req, expected_length = 5) ⇒ Object (private)



170
171
172
173
# File 'lib/rmodbus/server.rb', line 170

def parse_read_func(req, expected_length = 5)
  return nil if expected_length && req.length != expected_length
  { quant: req[3,2].unpack('n')[0], addr: req[1,2].unpack('n')[0] }
end

#parse_read_write_multiple_registers_func(req) ⇒ Object (private)



232
233
234
235
236
237
238
# File 'lib/rmodbus/server.rb', line 232

def parse_read_write_multiple_registers_func(req)
  return nil if req.length < 12
  params = { read: parse_read_func(req, nil),
    write: parse_write_multiple_registers_func(req[4..-1])}
  return nil if params[:write].nil?
  params
end

#parse_request(func, req) ⇒ Object (private)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/rmodbus/server.rb', line 67

def parse_request(func, req)
  case func
  when 1, 2, 3, 4
    parse_read_func(req)
  when 5
    parse_write_coil_func(req)
  when 6
    parse_write_register_func(req)
  when 15
    parse_write_multiple_coils_func(req)
  when 16
    parse_write_multiple_registers_func(req)
  when 22
    parse_mask_write_register_func(req)
  when 23
    parse_read_write_multiple_registers_func(req)
  end
end

#parse_response(func, res) ⇒ Object (private)



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/rmodbus/server.rb', line 86

def parse_response(func, res)
  if func & 0x80 == 0x80 && Funcs.include?(func & 0x7f)
    return nil unless res.length == 2
    return { err: res[1].ord }
  end

  case func
  when 1, 2
    return nil unless res.length == res[1].ord + 2
    res[2..-1].unpack_bits
  when 3, 4, 23
    return nil unless res.length == res[1].ord + 2
    res[2..-1].unpack('n*')
  when 5, 6, 15, 16
    return nil unless res.length == 5
    {}
  when 22
    return nil unless res.length == 7
    {}
  end
end

#parse_write_coil_func(req) ⇒ Object (private)



180
181
182
183
# File 'lib/rmodbus/server.rb', line 180

def parse_write_coil_func(req)
  return nil unless req.length == 5
  { addr: req[1,2].unpack('n')[0], val: req[3,2].unpack('n')[0] }
end

#parse_write_multiple_coils_func(req) ⇒ Object (private)



199
200
201
202
203
204
205
# File 'lib/rmodbus/server.rb', line 199

def parse_write_multiple_coils_func(req)
  return nil if req.length < 7
  params = parse_read_func(req, nil)
  return nil if req.length != 6 + (params[:quant] + 7) / 8
  params[:val] = req[6,params[:quant]].unpack_bits
  params
end

#parse_write_multiple_registers_func(req) ⇒ Object (private)



211
212
213
214
215
216
217
# File 'lib/rmodbus/server.rb', line 211

def parse_write_multiple_registers_func(req)
  return nil if req.length < 8
  params = parse_read_func(req, nil)
  return nil if req.length != 6 + params[:quant] * 2
  params[:val] = req[6,params[:quant] * 2].unpack('n*')
  params
end

#parse_write_register_func(req) ⇒ Object (private)



190
191
192
193
# File 'lib/rmodbus/server.rb', line 190

def parse_write_register_func(req)
  return nil unless req.length == 5
  { addr: req[1,2].unpack('n')[0], val: req[3,2].unpack('n')[0] }
end

#process_func(func, slave, req, params) ⇒ Object (private)



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/rmodbus/server.rb', line 108

def process_func(func, slave, req, params)
  case func
    when 1
      unless (err = validate_read_func(params, slave.coils, 2000))
        val = slave.coils[params[:addr],params[:quant]].pack_to_word
        pdu = func.chr + val.size.chr + val
      end
    when 2
      unless (err = validate_read_func(params, slave.discrete_inputs, 2000))
        val = slave.discrete_inputs[params[:addr],params[:quant]].pack_to_word
        pdu = func.chr + val.size.chr + val
      end
    when 3
      unless (err = validate_read_func(params, slave.holding_registers))
        pdu = func.chr + (params[:quant] * 2).chr + slave.holding_registers[params[:addr],params[:quant]].pack('n*')
      end
    when 4
      unless (err = validate_read_func(params, slave.input_registers))
        pdu = func.chr + (params[:quant] * 2).chr + slave.input_registers[params[:addr],params[:quant]].pack('n*')
      end
    when 5
      unless (err = validate_write_coil_func(params, slave))
        params[:val] = 1 if params[:val] == 0xff00
        slave.coils[params[:addr]] = params[:val]
        pdu = req
      end
    when 6
      unless (err = validate_write_register_func(params, slave))
        slave.holding_registers[params[:addr]] = params[:val]
        pdu = req
      end
    when 15
      unless (err = validate_write_multiple_coils_func(params, slave))
        slave.coils[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
        pdu = req[0,5]
      end
    when 16
      unless (err = validate_write_multiple_registers_func(params, slave))
        slave.holding_registers[params[:addr],params[:quant]] = params[:val]
        pdu = req[0,5]
      end
    when 22
      unless (err = validate_write_register_func(params, slave))
        addr = params[:addr]
        and_mask = params[:and_mask]
        slave.holding_registers[addr] = (slave.holding_registers[addr] & and_mask) | (params[:or_mask] & ~and_mask)
        pdu = req
      end
    when 23
      unless (err = validate_read_write_multiple_registers_func(params, slave))
        slave.holding_registers[params[:write][:addr],params[:write][:quant]] = params[:write][:val]
        pdu = func.chr + (params[:read][:quant] * 2).chr + slave.holding_registers[params[:read][:addr],params[:read][:quant]].pack('n*')
      end
  end

  if err
    (func | 0x80).chr + err.chr
  else
    pdu
  end
end

#slavesObject (private)



21
22
23
# File 'lib/rmodbus/server.rb', line 21

def slaves
  @slaves ||= {}
end

#validate_read_func(params, field, quant_max = 0x7d) ⇒ Object (private)



175
176
177
178
# File 'lib/rmodbus/server.rb', line 175

def validate_read_func(params, field, quant_max=0x7d)
  return 3 unless params[:quant] <= quant_max
  return 2 unless params[:addr] + params[:quant] <= field.size
end

#validate_read_write_multiple_registers_func(params, slave) ⇒ Object (private)



240
241
242
243
244
# File 'lib/rmodbus/server.rb', line 240

def validate_read_write_multiple_registers_func(params, slave)
  result = validate_read_func(params[:read], slave.holding_registers)
  return result if result
  validate_write_multiple_registers_func(params[:write], slave)
end

#validate_write_coil_func(params, slave) ⇒ Object (private)



185
186
187
188
# File 'lib/rmodbus/server.rb', line 185

def validate_write_coil_func(params, slave)
  return 2 unless params[:addr] <= slave.coils.size
  return 3 unless params[:val] == 0 or params[:val] == 0xff00
end

#validate_write_multiple_coils_func(params, slave) ⇒ Object (private)



207
208
209
# File 'lib/rmodbus/server.rb', line 207

def validate_write_multiple_coils_func(params, slave)
  validate_read_func(params, slave.coils)
end

#validate_write_multiple_registers_func(params, slave) ⇒ Object (private)



219
220
221
# File 'lib/rmodbus/server.rb', line 219

def validate_write_multiple_registers_func(params, slave)
  validate_read_func(params, slave.holding_registers)
end

#validate_write_register_func(params, slave) ⇒ Object (private)



195
196
197
# File 'lib/rmodbus/server.rb', line 195

def validate_write_register_func(params, slave)
  return 2 unless params[:addr] <= slave.holding_registers.size
end

#with_slave(uid) ⇒ Object



10
11
12
13
14
15
16
17
# File 'lib/rmodbus/server.rb', line 10

def with_slave(uid)
  slave = slaves[uid] ||= Server::Slave.new
  if block_given?
    yield slave
  else
    slave
  end
end