require 'ffi' # Represents libftdi ruby bindings. # End-user API represented by {Ftdi::Context} class. module Ftdi extend FFI::Library ffi_lib "libftdi" end
FFI::ManagedStruct
is responsible for the automatic collection of unmanaged resources: attach_function :ftdi_new, [ ], :pointer attach_function :ftdi_free, [ :pointer ], :void # Represents libftdi context and end-user API. # @example Open USB device # ctx = Ftdi::Context.new # begin # ctx.usb_open(0x0403, 0x6001) # begin # ctx.baudrate = 250000 # ensure # ctx.usb_close # end # rescue Ftdi::Error => e # $stderr.puts e.to_s # end class Context < FFI::ManagedStruct # layout skipped... # Initializes new libftdi context. # @raise [CannotInitializeContextError] libftdi cannot be initialized. def initialize ptr = Ftdi.ftdi_new raise CannotInitializeContextError.new if ptr.nil? super(ptr) end # Deinitialize and free an ftdi context. # @return [NilClass] nil def self.release(p) Ftdi.ftdi_free(p) nil end end
private def ctx self.to_ptr end def check_result(status_code) if status_code < 0 raise StatusCodeError.new(status_code, error_string) end nil end
error_string
, is the method that receives the error message from the libftdi context.ftdi_set_interface
. From what we dance: enum ftdi_interface { INTERFACE_ANY = 0, INTERFACE_A = 1, INTERFACE_B = 2, INTERFACE_C = 3, INTERFACE_D = 4 }; int ftdi_set_interface(struct ftdi_context *ftdi, enum ftdi_interface interface);
# Port interface for chips with multiple interfaces. # @see Ftdi::Context#interface= Interface = enum(:interface_any, :interface_a, :interface_b, :interface_c, :interface_d) attach_function :ftdi_set_interface, [ :pointer, Interface ], :int class Context # ... # Open selected channels on a chip, otherwise use first channel. # @param [Interface] new_interface Interface to use for FT2232C/2232H/4232H chips. # @raise [StatusCodeError] libftdi reports error. # @return [Interface] New interface. def interface=(new_interface) check_result(Ftdi.ftdi_set_interface(ctx, new_interface)) new_interface end ... end
:string
), trying to use them to transfer an array of bytes is doomed to failure, since the FFI marshaller stumbles on the first zero byte.:pointer
, which we will form via FFI :: MemoryPointer (allocating and filling the corresponding buffer in memory). attach_function :ftdi_write_data, [ :pointer, :pointer, :int ], :int class Context # ... # Writes data. # @param [String, Array] bytes String or array of integers that will be interpreted as bytes using pack('c*'). # @return [Fixnum] Number of written bytes. # @raise [StatusCodeError] libftdi reports error. def write_data(bytes) bytes = bytes.pack('c*') if bytes.respond_to?(:pack) size = bytes.respond_to?(:bytesize) ? bytes.bytesize : bytes.size mem_buf = FFI::MemoryPointer.new(:char, size) mem_buf.put_bytes(0, bytes) bytes_written = Ftdi.ftdi_write_data(ctx, mem_buf, size) check_result(bytes_written) bytes_written end end
Source: https://habr.com/ru/post/142172/
All Articles