lib/ligo/device.rb in ligo-0.1.0.beta vs lib/ligo/device.rb in ligo-0.1.0

- old
+ new

@@ -17,17 +17,53 @@ module Ligo require 'ligo/constants' + # This class provides a convenient wrapper class around `LIBUSB::Device` and + # implements the Android Open Accessory Protocol to interact with compatible + # devices. + # + # This class is a derivative work of `LIBUSB::Device` as included in + # [LIBUSB](https://github.com/larskanis/libusb), written by Lars Kanis and + # released under the LGPLv3. + # @author Renaud AUBIN + # @api public class Device < LIBUSB::Device include Logging - # TODO: Document the attr! - attr_reader :pDev, :pDevDesc - attr_reader :aoap_version, :accessory, :in, :out, :handle + # @api private + attr_reader :pDev + # @api private + attr_reader :pDevDesc + + # Returns the version of the AOA protocol that this device supports + # @return [Fixnum] the version of the AOA protocol that this device + # supports. + attr_reader :aoap_version + + # Returns the associated {Accessory} + # @return [Accessory, nil] the associated accessory if any or nil. + attr_reader :accessory + + # Returns the accessory mode input endpoint + # @return [LIBUSB::Endpoint, nil] the input endpoint or nil if the device is + # not in accessory mode. + attr_reader :in + + # Returns the accessory mode output endpoint + # @return [LIBUSB::Endpoint, nil] the output endpoint or nil if the device + # is not in accessory mode. + attr_reader :out + + # Returns the device handle + # @todo Improve the :handle doc + # @return [LIBUSB::DevHandle, nil] the device handle or nil. + attr_reader :handle + + # @api private def initialize context, pDev @aoap_version = 0 @accessory, @in, @out, @handle = nil, nil, nil, nil super context, pDev end @@ -46,25 +82,31 @@ # close raise Interrupt, msg end end + # Opens an handle and claim the default interface for further operations + # @return [LIBUSB::DevHandle] the handle to operate on. + # @raise def open_and_claim @handle = open @handle.claim_interface(0) @handle.clear_halt(@in) @handle end + # Finalizes the device (release and close) + # @return + # @raise [LIBUSB::ERROR_TIMEOUT] in case of timeout. def finalize if @handle @handle.release_interface(0) @handle.close end end - # Simple write method (blocking until timeout). + # Simple write method (blocking until timeout) # @param [Fixnum] buffer_size # The number of bytes expected to be received. # @param [Fixnum] timeout # The timeout in ms (default: 1000). 0 for an infinite timeout. # @return [String] the received buffer (at most buffer_size bytes). @@ -72,12 +114,13 @@ def read(buffer_size, timeout = 1000) handle.bulk_transfer(endpoint: @in, dataIn: buffer_size, timeout: timeout) end + alias_method :recv, :read - # Simple write method (blocking until timeout). + # Simple write method (blocking until timeout) # @param [String] buffer # The buffer to be sent. # @param [Fixnum] timeout # The timeout in ms (default: 1000). 0 for an infinite timeout. # @return [Fixnum] the number of bytes actually sent. @@ -85,36 +128,13 @@ def write(buffer, timeout = 1000) handle.bulk_transfer(endpoint: @out, dataOut: buffer, timeout: timeout) end + alias_method :send, :write - # Simple recv method. - # @param [Fixnum] buffer_size - # The buffer size of the received buffer. - # @return [String] the received buffer (at most buffer_size bytes). - def recv(buffer_size) - begin - handle.bulk_transfer(endpoint: @in, - dataIn: buffer_size) - rescue LIBUSB::ERROR_TIMEOUT - nil - # maybe we should implement a internal thread, a sleep and a retry - end - end - - # Simple send method. - # @param [String] data - # The data to be sent. - # @return [Fixnum] the number of bytes sent. - def send(data) - # TODO: Add timeout param? - handle.bulk_transfer(endpoint: @out, dataOut: data) - end - - # Associate an AOAP compatible device with a virtual accessory and switch the Android device - # to accessory mode. + # Associates with an accessory and switch to accessory mode # # Prepare an OAP compatible device to interact with a given {Ligo::Accessory}: # * Switch the current assigned device to accessory mode # * Set the I/O endpoints # @param [Ligo::Accessory] accessory @@ -151,12 +171,15 @@ end end true end + # Switches to accessory mode + # # Send identifying string information to the device and request the device start up in accessory # mode. + # @return [true, false] true for success, false otherwise. def start_accessory_mode logger.debug 'start_accessory_mode' sn = self.serial_number self.open_interface(0) do |handle| @@ -167,10 +190,12 @@ end wait_and_retrieve_by_serial(sn) end + # Sends a `set configuration` control transfer + # # Set the device's configuration to a value of 1 with a SET_CONFIGURATION (0x09) device # request. # @return [true, false] true for success, false otherwise. def set_configuration logger.debug 'set_configuration' @@ -191,41 +216,43 @@ wait_and_retrieve_by_serial(sn) res == 0 end end - # Check if the current {Ligo::Device} is in accessory mode. - # @return [true, false] true if the {Ligo::Device} is in accessory mode, - # false otherwise. + # Check if the current {Device} is in accessory mode + # @return [true, false] true if the {Device} is in accessory mode, false + # otherwise. def accessory_mode? self.idVendor == GOOGLE_VID end - # Check if the current {Ligo::Device} supports AOAP. + # Check if the current {Device} supports AOAP # @return [true, false] true if the {Ligo::Device} supports AOAP, false # otherwise. def aoap? @aoap_version = self.get_protocol logger.info "#{self.inspect} supports AOAP version #{@aoap_version}." @aoap_version >= 1 end - # Check if the current {Ligo::Device} is in UMS mode. - # @return [true, false] true if the {Ligo::Device} is in UMS mode, false - # otherwise. + # Check if the current {Device} is in UMS mode + # @return [true, false] true if the {Device} is in UMS mode, false otherwise def uas? if RUBY_PLATFORM=~/linux/i # http://cateee.net/lkddb/web-lkddb/USB_UAS.html (self.settings[0].bInterfaceClass == 0x08) && (self.settings[0].bInterfaceSubClass == 0x06) else false end end + # Sends a `get protocol` control transfer + # # Send a 51 control request ("Get Protocol") to figure out if the device - # supports the Android accessory protocol. + # supports the Android accessory protocol. We assume here that the device + # has not been opened. # @return [Fixnum] the AOAP protocol version supported by the device (0 for # no AOAP support). def get_protocol logger.debug 'get_protocol' res, version = 0, 0 @@ -241,11 +268,15 @@ end (res.size == 2 && version >= 1 ) ? version : 0 end - # Send identifying string information to the device. + # Sends identifying string information to the device + # + # We assume here that the device has already been opened. + # @api private + # @return def send_accessory_id logger.debug 'send_accessory_id' req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_VENDOR @accessory.each do |k,v| # Ensure the string is terminated by a null char @@ -258,21 +289,24 @@ logger.error "Failed to send #{k} string" unless r == s.size end end private :send_accessory_id - # Request the device start up in accessory mode + # Sends AOA protocol start command to the device + # @api private + # @return [Fixnum] def send_start logger.debug 'send_start' req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_VENDOR res = @handle.control_transfer(bmRequestType: req_type, bRequest: COMMAND_START, wValue: 0x0, wIndex: 0x0, dataOut: nil) end private :send_start - # Internal use only. + # @api private + # @return [true, false] true for success, false otherwise. def wait_and_retrieve_by_serial(sn) sleep REENUMERATION_DELAY # The device should now reappear on the usb bus with the Google vendor id. # We retrieve it by using its serial number. device = @context.devices(idVendor: GOOGLE_VID).collect do |d| @@ -281,17 +315,19 @@ if device # Retrieve new pointers (check if the old ones should be dereferenced) @pDev = device.pDev @pDevDesc = device.pDevDesc + true else logger.error ['Failed to retrieve the device after switching to ', 'accessory mode. This may be due to a lack of proper ', 'permissions ⇒ check your udev rules.', "\n", 'The Google vendor id rule may look like:', "\n", 'SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ', 'MODE="0666", GROUP="plugdev"' ].join + false end end private :wait_and_retrieve_by_serial end