class Modbus

Constants

ADU

TCP ADU are sent via TCP to registered port 502

CODES
EXCEPTIONS
MULTIPLE_CODES
READ_CODES
WRITE_CODES

Public Class Methods

new() click to toggle source
# File lib/modbus.rb, line 43
def initialize
    @transaction = 0
end

Public Instance Methods

read(data, serial: false) { |adu header, function_code, response| ... } click to toggle source

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

@param data [String] The bytes to decode.

# File lib/modbus.rb, line 50
def read(data, serial: false)
    @buffer ||= String.new
    # add mock TCP header
    if serial
        @buffer << "\x0\x0\x0\x0\x0"
        @buffer << (data.bytesize - 2)
    end
    @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) click to toggle source
# File lib/modbus.rb, line 106
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) click to toggle source
# File lib/modbus.rb, line 120
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

Protected Instance Methods

new_header() click to toggle source
# File lib/modbus.rb, line 177
def new_header
    header = TCPHeader.new
    header.transaction_identifier = next_id
    header.protocol_identifier = 0
    header
end
next_id() click to toggle source
# File lib/modbus.rb, line 170
def next_id
    id = @transaction
    @transaction += 1
    @transaction = 0 if @transaction > 0xFFFF
    id
end
request_adu(function) click to toggle source
# File lib/modbus.rb, line 192
def request_adu(function)
    code = CODES[function]
    ADU.new(new_header, code, request_pdu(code))
end
request_pdu(code) click to toggle source
# File lib/modbus.rb, line 184
def request_pdu(code)
    request = RequestPDU.new
    # This seems to be the default
    request.unit_identifier = 0xFF
    request.function_code = code
    request
end
write_multiple_coils(address, *values) click to toggle source
# File lib/modbus.rb, line 141
def write_multiple_coils(address, *values)
    size = values.length

    bytes = []
    byte = 0
    bit = 0
    loop do
        value = values.shift
        if value
            byte = byte | (1 << bit)
        end
        break if values.empty?
        bit += 1
        if bit >= 8
            bytes << byte
            byte = 0
            bit = 0
        end
    end
    bytes << byte

    adu = request_adu :write_multiple_coils
    request = adu.pdu
    request.put_multiple.address = address.to_i
    request.put_multiple.quantity = size
    request.put_multiple.data = bytes.pack('C*')
    adu
end