Class: ModBus::Client::Slave

Inherits:
Object
  • Object
show all
Includes:
Debug, Errors, Options
Defined in:
lib/rmodbus/client/slave.rb

Direct Known Subclasses

RTUSlave, TCPSlave

Constant Summary collapse

Exceptions =
{
      1 => IllegalFunction.new("The function code received in the query is not an allowable action for the server"),
      2 => IllegalDataAddress.new("The data address received in the query is not an allowable address for the server"),
      3 => IllegalDataValue.new("A value contained in the query data field is not an allowable value for server"),
      4 => SlaveDeviceFailure.new("An unrecoverable error occurred while the server was attempting to perform the requested action"),
      5 => Acknowledge.new("The server has accepted the request and is processing it, but a long duration of time will be required to do so"),
      6 => SlaveDeviceBus.new("The server is engaged in processing a long duration program command"),
      8 => MemoryParityError.new("The extended file area failed to pass a consistency check")
}

Instance Attribute Summary collapse

Attributes included from Options

#raise_exception_on_mismatch, #read_retries, #read_retry_timeout

Attributes included from Debug

#logger, #raise_exception_on_mismatch, #read_retries, #read_retry_timeout

Instance Method Summary collapse

Methods included from Debug

#log, #logging_bytes

Constructor Details

#initialize(uid, io) ⇒ Slave

Returns a new instance of Slave.



20
21
22
23
# File 'lib/rmodbus/client/slave.rb', line 20

def initialize(uid, io)
  @uid = uid
  @io = io
end

Instance Attribute Details

#uidObject

Number of times to retry on read and read timeouts



10
11
12
# File 'lib/rmodbus/client/slave.rb', line 10

def uid
  @uid
end

Instance Method Details

#check_response_mismatch(request, response) ⇒ Object (private)

Raises:



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/rmodbus/client/slave.rb', line 293

def check_response_mismatch(request, response)
  read_func = response.getbyte(0)
  data = response[2..-1]
  #Mismatch functional code
  send_func = request.getbyte(0)
  if read_func != send_func
    msg = "Function code is mismatch (expected #{send_func}, got #{read_func})"
  end

  case read_func
  when 1,2
    bc = request.getword(3)/8 + 1
    if data.size != bc
      msg = "Byte count is mismatch (expected #{bc}, got #{data.size} bytes)"
    end
  when 3,4
    rc = request.getword(3)
    if data.size/2 != rc
      msg = "Register count is mismatch (expected #{rc}, got #{data.size/2} regs)"
    end
  when 5,6
    exp_addr = request.getword(1)
    got_addr = response.getword(1)
    if exp_addr != got_addr
      msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
    end

    exp_val = request.getword(3)
    got_val = response.getword(3)
    if exp_val != got_val
      msg = "Value is mismatch (expected 0x#{exp_val.to_s(16)}, got 0x#{got_val.to_s(16)})"
    end
  when 15,16
    exp_addr = request.getword(1)
    got_addr = response.getword(1)
    if exp_addr != got_addr
      msg = "Address is mismatch (expected #{exp_addr}, got #{got_addr})"
    end

    exp_quant = request.getword(3)
    got_quant = response.getword(3)
    if exp_quant != got_quant
      msg = "Quantity is mismatch (expected #{exp_quant}, got #{got_quant})"
    end
  else
    warn "Fuiction (#{read_func}) is not supported raising response mismatch"
  end

  raise ResponseMismatch.new(msg, request, response) if msg
end

#coilsReadWriteProxy

Returns a ModBus::ReadWriteProxy hash interface for coils

Examples:

coils[addr] => [1]
coils[addr1..addr2] => [1, 0, ..]
coils[addr] = 0 => [0]
coils[addr1..addr2] = [1, 0, ..] => [1, 0, ..]

Returns:



34
35
36
# File 'lib/rmodbus/client/slave.rb', line 34

def coils
  ModBus::ReadWriteProxy.new(self, :coil)
end

#discrete_inputsReadOnlyProxy

Returns a ModBus::ReadOnlyProxy hash interface for discrete inputs

Examples:

discrete_inputs[addr] => [1]
discrete_inputs[addr1..addr2] => [1, 0, ..]

Returns:



105
106
107
# File 'lib/rmodbus/client/slave.rb', line 105

def discrete_inputs
  ModBus::ReadOnlyProxy.new(self, :discrete_input)
end

#holding_registersReadWriteProxy

Returns a ModBus::ReadWriteProxy hash interface for holding registers

Examples:

holding_registers[addr] => [123]
holding_registers[addr1..addr2] => [123, 234, ..]
holding_registers[addr] = 123 => 123
holding_registers[addr1..addr2] = [234, 345, ..] => [234, 345, ..]

Returns:



161
162
163
# File 'lib/rmodbus/client/slave.rb', line 161

def holding_registers
  ModBus::ReadWriteProxy.new(self, :holding_register)
end

#input_registersReadOnlyProxy

Returns a read/write ModBus::ReadOnlyProxy hash interface for coils

Examples:

input_registers[addr] => [1]
input_registers[addr1..addr2] => [1, 0, ..]

Returns:



132
133
134
# File 'lib/rmodbus/client/slave.rb', line 132

def input_registers
  ModBus::ReadOnlyProxy.new(self, :input_register)
end

#mask_write_register(addr, and_mask, or_mask) ⇒ Object

Mask a holding register

Examples:

mask_write_register(1, 0xAAAA, 0x00FF) => self

Parameters:

  • addr (Integer)

    address registers

  • and_mask (Integer)

    mask for AND operation

  • or_mask (Integer)

    mask for OR operation



222
223
224
225
# File 'lib/rmodbus/client/slave.rb', line 222

def mask_write_register(addr, and_mask, or_mask)
  query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
  self
end

#query(request) ⇒ String

Request pdu to slave device

Parameters:

  • pdu (String)

    request to slave

Returns:

Raises:

  • (ResponseMismatch)

    the received echo response differs from the request

  • (ModBusTimeout)

    timed out during read attempt

  • (ModBusException)

    unknown error

  • (IllegalFunction)

    function code received in the query is not an allowable action for the server

  • (IllegalDataAddress)

    data address received in the query is not an allowable address for the server

  • (IllegalDataValue)

    value contained in the query data field is not an allowable value for server

  • (SlaveDeviceFailure)

    unrecoverable error occurred while the server was attempting to perform the requested action

  • (Acknowledge)

    server has accepted the request and is processing it, but a long duration of time will be required to do so

  • (SlaveDeviceBus)

    server is engaged in processing a long duration program command

  • (MemoryParityError)

    extended file area failed to pass a consistency check



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/rmodbus/client/slave.rb', line 263

def query(request)
  tried = 0
  response = ""
  begin
    ::Timeout.timeout(@read_retry_timeout, ModBusTimeout) do
      send_pdu(request)
      response = read_pdu unless uid == 0
    end
  rescue ModBusTimeout => err
    log "Timeout of read operation: (#{@read_retries - tried})"
    tried += 1
    retry unless tried >= @read_retries
    raise ModBusTimeout.new, "Timed out during read attempt"
  end

  return nil if response.size == 0

  read_func = response.getbyte(0)
  if read_func >= 0x80
    exc_id = response.getbyte(1)
    raise Exceptions[exc_id] unless Exceptions[exc_id].nil?

    raise ModBusException.new, "Unknown error"
  end

  check_response_mismatch(request, response) if raise_exception_on_mismatch
  response[2..-1]
end

#read_coil(addr) ⇒ Object



50
51
52
# File 'lib/rmodbus/client/slave.rb', line 50

def read_coil(addr)
  read_coils(addr, 1).first
end

#read_coils(addr, ncoils = 1) ⇒ Array

Read coils

Examples:

read_coils(addr, ncoils) => [1, 0, ..]

Parameters:

  • addr (Integer)

    address first coil

  • ncoils (Integer) (defaults to: 1)

    number coils

Returns:



46
47
48
# File 'lib/rmodbus/client/slave.rb', line 46

def read_coils(addr, ncoils = 1)
  query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
end

#read_discrete_input(addr) ⇒ Object



121
122
123
# File 'lib/rmodbus/client/slave.rb', line 121

def read_discrete_input(addr)
  read_discrete_inputs(addr, 1).first
end

#read_discrete_inputs(addr, ninputs = 1) ⇒ Array

Read discrete inputs

@param ninputs number inputs

Examples:

read_discrete_inputs(addr, ninputs) => [1, 0, ..]

Parameters:

  • addr (Integer)

    address first input

Returns:



117
118
119
# File 'lib/rmodbus/client/slave.rb', line 117

def read_discrete_inputs(addr, ninputs = 1)
  query("\x2" + addr.to_word + ninputs.to_word).unpack_bits[0..ninputs-1]
end

#read_holding_register(addr) ⇒ Object



177
178
179
# File 'lib/rmodbus/client/slave.rb', line 177

def read_holding_register(addr)
  read_holding_registers(addr, 1).first
end

#read_holding_registers(addr, nregs = 1) ⇒ Array

Read holding registers

Examples:

read_holding_registers(1, 5) => [1, 0, ..]

Parameters:

  • addr (Integer)

    address first registers

  • nregs (Integer) (defaults to: 1)

    number registers

Returns:



173
174
175
# File 'lib/rmodbus/client/slave.rb', line 173

def read_holding_registers(addr, nregs = 1)
  query("\x3" + addr.to_word + nregs.to_word).unpack('n*')
end

#read_input_register(addr) ⇒ Object



148
149
150
# File 'lib/rmodbus/client/slave.rb', line 148

def read_input_register(addr)
  read_input_registers(addr, 1).first
end

#read_input_registers(addr, nregs = 1) ⇒ Array

Read input registers

Examples:

read_input_registers(1, 5) => [1, 0, ..]

Parameters:

  • addr (Integer)

    address first registers

  • nregs (Integer) (defaults to: 1)

    number registers

Returns:



144
145
146
# File 'lib/rmodbus/client/slave.rb', line 144

def read_input_registers(addr, nregs = 1)
  query("\x4" + addr.to_word + nregs.to_word).unpack('n*')
end

#read_write_multiple_registers(addr_r, nregs, addr_w, vals) ⇒ Array Also known as: read_write_holding_registers

Read/write multiple holding registers

Examples:

read_write_multiple_registers(1, 5, 1, [0xaa, 0]) => [1, 0, ..]

Parameters:

  • addr_r (Integer)

    address first registers to read

  • nregs (Integer)

    number registers to read

  • addr_w (Integer)

    address first registers to write

  • vals (Array)

    written registers

Returns:



237
238
239
240
241
242
243
244
245
# File 'lib/rmodbus/client/slave.rb', line 237

def read_write_multiple_registers(addr_r, nregs, addr_w, vals)
  s_val = ""
  vals.each do |reg|
    s_val << reg.to_word
  end

  query("\x17" + addr_r.to_word + nregs.to_word +
            addr_w.to_word + vals.size.to_word + (vals.size * 2).chr + s_val).unpack('n*')
end

#write_multiple_coils(addr, vals) ⇒ Object Also known as: write_coils

Write multiple coils

Examples:

write_multiple_coils(1, [0,1,0,1]) => self

Parameters:

  • addr (Integer)

    address first coil

  • vals (Array)

    written coils



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rmodbus/client/slave.rb', line 79

def write_multiple_coils(addr, vals)
  nbyte = ((vals.size-1) >> 3) + 1
  sum = 0
  (vals.size - 1).downto(0) do |i|
    sum = sum << 1
    sum |= 1 if vals[i] > 0
  end

  s_val = ""
  nbyte.times do
    s_val << (sum & 0xff).chr
    sum >>= 8
  end

  query("\xf" + addr.to_word + vals.size.to_word + nbyte.chr + s_val)
  self
end

#write_multiple_registers(addr, vals) ⇒ Object Also known as: write_holding_registers

Write multiple holding registers

Examples:

write_multiple_registers(1, [0xaa, 0]) => self

Parameters:

  • addr (Integer)

    address first registers

  • val (Array)

    written registers

Returns:

  • self



204
205
206
207
208
209
210
211
212
# File 'lib/rmodbus/client/slave.rb', line 204

def write_multiple_registers(addr, vals)
  s_val = ""
  vals.each do |reg|
    s_val << reg.to_word
  end

  query("\x10" + addr.to_word + vals.size.to_word + (vals.size * 2).chr + s_val)
  self
end

#write_single_coil(addr, val) ⇒ Object Also known as: write_coil

Write a single coil

Examples:

write_single_coil(1, 0) => self

Parameters:

  • addr (Integer)

    address coil

  • val (Integer)

    value coil (0 or other)

Returns:

  • self



62
63
64
65
66
67
68
69
# File 'lib/rmodbus/client/slave.rb', line 62

def write_single_coil(addr, val)
  if val == 0
    query("\x5" + addr.to_word + 0.to_word)
  else
    query("\x5" + addr.to_word + 0xff00.to_word)
  end
  self
end

#write_single_register(addr, val) ⇒ Object Also known as: write_holding_register

Write a single holding register

Examples:

write_single_register(1, 0xaa) => self

Parameters:

  • addr (Integer)

    address registers

  • val (Integer)

    written to register

Returns:

  • self



189
190
191
192
# File 'lib/rmodbus/client/slave.rb', line 189

def write_single_register(addr, val)
  query("\x6" + addr.to_word + val.to_word)
  self
end