Class: Modbus

Inherits:
Object
  • Object
show all
Defined in:
lib/modbus.rb,
lib/modbus/adu.rb,
lib/modbus/request.rb,
lib/modbus/response.rb,
lib/modbus/exception.rb,
lib/modbus/tcp_header.rb

Defined Under Namespace

Classes: ExceptionPDU, RequestPDU, ResponsePDU, TCPHeader

Constant Summary collapse

CODES =
{
    read_coils: 0x01,
    read_inputs: 0x02,
    read_holding_registers: 0x03,
    read_input_registers: 0x04,
    write_coil: 0x05,
    write_register: 0x06,
    write_multiple_coils: 0x0F,
    write_multiple_registers: 0x10,

    # Serial only
    read_exception_status: 0x07,
    diagnostics: 0x08,
    get_event_counter: 0x0B,
    get_event_log: 0x0C,
    get_server_id: 0x11,

    # unsupported
    read_file_record: 0x14,
    write_file_record: 0x15,
    mask_write_register: 0x16,
    read_write_registers: 0x17,
    read_fifo_queue: 0x17,
    encapsulated_interface_transport: 0x2B,
    canopen_general_request: 0x0D,
    read_device_identification: 0x0E
}
READ_CODES =
[0x01, 0x02, 0x03, 0x04].freeze
WRITE_CODES =
[0x05, 0x06].freeze
MULTIPLE_CODES =
[0x0F, 0x10].freeze
EXCEPTIONS =
{
    0x01 => 'illegal function',
    0x02 => 'illegal data address',
    0x03 => 'illegal data value',
    0x04 => 'server device failure',
    0x05 => 'acknowledge', # processing will take some time, no need to retry
    0x06 => 'server device busy',
    0x08 => 'memory parity error',
    0x0A => 'gateway path unavailable',
    0x0B => 'gateway device failed to respond'
}

Instance Method Summary collapse

Constructor Details

#initializeModbus

Returns a new instance of Modbus.



43
44
45
# File 'lib/modbus.rb', line 43

def initialize
    @transaction = 0
end

Instance Method Details

#read(data) ⇒ Object

Decodes an ADU from wire format and sets the attributes of this object.

Parameters:

  • data (String)

    The bytes to decode.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/modbus.rb', line 50

def read(data)
    @buffer ||= String.new
    @buffer << data

    error = nil

    loop do
        # not enough data in buffer to know the length
        break if @buffer.bytesize < 6

        header = TCPHeader.new
        header.read(@buffer[0..5])

        # the headers unit identifier is included in the length
        total_length = header.request_length + 6

        # Extract just the request from the buffer
        break if @buffer.bytesize < total_length
        request = @buffer.slice!(0...total_length)
        function_code = request.getbyte(7)

        # Yield the complete responses
        begin
            if function_code <= 0x80
                response = ResponsePDU.new
                response.read(request[6..-1])
            else # Error response
                response = ExceptionPDU.new
                response.read(request[6..-1])
                function_code = function_code - 0x80
            end

            yield ADU.new header, function_code, response
        rescue => e
            error = e
        end
    end

    raise error if error
end

#write_coils(address, *values) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/modbus.rb', line 101

def write_coils(address, *values)
    values = values.flatten

    if values.length > 1
        write_multiple_coils(address, *values)
    else
        adu = request_adu :write_coil
        request = adu.pdu
        request.put.address = address.to_i
        request.put.data = values.first ? 0xFF00 : 0x0
        adu
    end
end

#write_registers(address, *values) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/modbus.rb', line 115

def write_registers(address, *values)
    values = values.flatten.map! { |value| value.to_i }

    if values.length > 1
        adu = request_adu :write_multiple_registers
        request = adu.pdu
        request.put_multiple.address = address.to_i
        request.put_multiple.quantity = values.length
        request.put_multiple.data = values.pack('n*')
        adu
    else
        adu = request_adu :write_register
        request = adu.pdu
        request.put.address = address.to_i
        request.put.data = values.first
        adu
    end
end