Class: Ligo::Device

Inherits:
LIBUSB::Device
  • Object
show all
Includes:
Logging
Defined in:
lib/ligo/device.rb

Overview

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, written by Lars Kanis and released under the LGPLv3.

Author:

  • Renaud AUBIN

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

configure_logger_for, configure_logger_output, #logger, logger_for

Constructor Details

#initialize(context, pDev) ⇒ Device

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Device.



65
66
67
68
69
# File 'lib/ligo/device.rb', line 65

def initialize context, pDev
  @aoap_version = 0
  @accessory, @in, @out, @handle = nil, nil, nil, nil
  super context, pDev
end

Instance Attribute Details

#accessoryAccessory? (readonly)

Returns the associated Accessory

Returns:

  • (Accessory, nil)

    the associated accessory if any or nil.



47
48
49
# File 'lib/ligo/device.rb', line 47

def accessory
  @accessory
end

#aoap_versionFixnum (readonly)

Returns the version of the AOA protocol that this device supports

Returns:

  • (Fixnum)

    the version of the AOA protocol that this device supports.



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

def aoap_version
  @aoap_version
end

#handleLIBUSB::DevHandle? (readonly)

TODO:

Improve the :handle doc

Returns the device handle

Returns:

  • (LIBUSB::DevHandle, nil)

    the device handle or nil.



62
63
64
# File 'lib/ligo/device.rb', line 62

def handle
  @handle
end

#inLIBUSB::Endpoint? (readonly)

Returns the accessory mode input endpoint

Returns:

  • (LIBUSB::Endpoint, nil)

    the input endpoint or nil if the device is not in accessory mode.



52
53
54
# File 'lib/ligo/device.rb', line 52

def in
  @in
end

#outLIBUSB::Endpoint? (readonly)

Returns the accessory mode output endpoint

Returns:

  • (LIBUSB::Endpoint, nil)

    the output endpoint or nil if the device is not in accessory mode.



57
58
59
# File 'lib/ligo/device.rb', line 57

def out
  @out
end

#pDevObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



35
36
37
# File 'lib/ligo/device.rb', line 35

def pDev
  @pDev
end

#pDevDescObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



38
39
40
# File 'lib/ligo/device.rb', line 38

def pDevDesc
  @pDevDesc
end

Instance Method Details

#accessory_mode?true, false

Check if the current Ligo::Device is in accessory mode

Returns:

  • (true, false)

    true if the Ligo::Device is in accessory mode, false otherwise.



222
223
224
# File 'lib/ligo/device.rb', line 222

def accessory_mode?
  (self.idVendor == GOOGLE_VID) && (GOOGLE_PIDS.include? self.idProduct)
end

#aoap?true, false

Check if the current Ligo::Device supports AOAP

Returns:

  • (true, false)

    true if the Ligo::Device supports AOAP, false otherwise.



229
230
231
232
233
234
235
236
237
238
# File 'lib/ligo/device.rb', line 229

def aoap?
  @aoap_version = self.get_protocol
  aoap_supported = (@aoap_version >= 1)
  if aoap_supported
    logger.info "#{self.inspect} supports AOA Protocol version #{@aoap_version}."
  else
    logger.info "#{self.inspect} doesn't support AOA Protocol."
  end
  aoap_supported
end

#attach_accessory(accessory) ⇒ true, false

Associates with an accessory and switch to accessory mode

Prepare an OAP compatible device to interact with a given Accessory:

  • Switch the current assigned device to accessory mode
  • Set the I/O endpoints

Parameters:

  • accessory (Ligo::Accessory)

    The virtual accessory to be associated with the Android device.

Returns:

  • (true, false)

    true for success, false otherwise.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/ligo/device.rb', line 143

def attach_accessory(accessory)
  logger.debug "attach_accessory(#{accessory})"

  @accessory = accessory

  if accessory_mode?
    # if the device is already in accessory mode, we send
    # set_configuration to force an usb attached event on the device
    begin
      set_configuration
    rescue LIBUSB::ERROR_NO_DEVICE
      logger.debug '  set_configuration raises LIBUSB::ERROR_NO_DEVICE - Retry'
      sleep REENUMERATION_DELAY
      # Set configuration may fail
      retry
    end
  else
    # the device is not in accessory mode, start_accessory_mode is
    # sufficient to get an usb attached event on the device
    return false unless start_accessory_mode
  end

  # Find out the in/out endpoints
  self.interfaces.first.endpoints.each do |ep|
    if ep.bEndpointAddress & 0b10000000 == 0
      @out = ep if @out.nil?
    else
      @in = ep if @in.nil?
    end
  end
  true
end

#finalizeObject

Finalizes the device (release and close)

Returns:

Raises:

  • (LIBUSB::ERROR_TIMEOUT)

    in case of timeout.



100
101
102
103
104
105
# File 'lib/ligo/device.rb', line 100

def finalize
  if @handle
    @handle.release_interface(0)
    @handle.close
  end
end

#get_device(sn) ⇒ LIBUSB::Device

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retrieves an AOAP device by its serial number

Parameters:

  • sn (String)

    The serial number of the device to be found.

Returns:

  • (LIBUSB::Device)

    the device matching the given serial number.



316
317
318
319
320
# File 'lib/ligo/device.rb', line 316

def get_device(sn)
  device = @context.devices(idVendor: GOOGLE_VID).collect do |d|
    d.serial_number == sn ? d : nil
  end.compact.first
end

#get_protocolFixnum

Sends a get protocol control transfer

Send a 51 control request ("Get Protocol") to figure out if the device supports the Android accessory protocol. We assume here that the device has not been opened.

Returns:

  • (Fixnum)

    the AOAP protocol version supported by the device (0 for no AOAP support).



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/ligo/device.rb', line 259

def get_protocol
  logger.debug 'get_protocol'
  res, version = 0, 0
  self.open do |h|

    h.detach_kernel_driver(0) if self.uas? && h.kernel_driver_active?(0)
    req_type = LIBUSB::ENDPOINT_IN | LIBUSB::REQUEST_TYPE_VENDOR
    res = h.control_transfer(bmRequestType: req_type,
                             bRequest: COMMAND_GETPROTOCOL,
                             wValue: 0x0, wIndex: 0x0, dataIn: 2)

    version = res.unpack('S')[0]
  end

  (res.size == 2 && version >= 1 ) ? version : 0
rescue LIBUSB::ERROR_NOT_SUPPORTED, LIBUSB::ERROR_PIPE
  0
end

#open_and_claimLIBUSB::DevHandle

Opens an handle and claim the default interface for further operations

Returns:

  • (LIBUSB::DevHandle)

    the handle to operate on.

Raises:



90
91
92
93
94
95
# File 'lib/ligo/device.rb', line 90

def open_and_claim
  @handle = open
  @handle.claim_interface(0)
  @handle.clear_halt(@in)
  @handle
end

#process(&block) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ligo/device.rb', line 71

def process(&block)
  begin
    self.open_interface(0) do |handle|
      @handle = handle
      yield handle
      @handle = nil
    end
    # close
  rescue LIBUSB::ERROR_NO_DEVICE
    msg =  'The target device has been disconnected'
    logger.debug msg
    # close
    raise Interrupt, msg
  end
end

#read(buffer_size, timeout = 1000) ⇒ String Also known as: recv

Simple write method (blocking until timeout)

Parameters:

  • buffer_size (Fixnum)

    The number of bytes expected to be received.

  • timeout (Fixnum) (defaults to: 1000)

    The timeout in ms (default: 1000). 0 for an infinite timeout.

Returns:

  • (String)

    the received buffer (at most buffer_size bytes).

Raises:

  • (LIBUSB::ERROR_TIMEOUT)

    in case of timeout.



114
115
116
117
118
# File 'lib/ligo/device.rb', line 114

def read(buffer_size, timeout = 1000)
  handle.bulk_transfer(endpoint: @in,
                       dataIn: buffer_size,
                       timeout: timeout)
end

#set_configurationtrue, false

Sends a set configuration control transfer

Set the device's configuration to a value of 1 with a SET_CONFIGURATION (0x09) device request.

Returns:

  • (true, false)

    true for success, false otherwise.



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/ligo/device.rb', line 200

def set_configuration
  logger.debug 'set_configuration'
  res = nil
  sn = self.serial_number
  device = get_device(sn)

  begin
    device.open_interface(0) do |handle|
      req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_STANDARD
      res = handle.control_transfer(bmRequestType: req_type,
                                    bRequest: LIBUSB::REQUEST_SET_CONFIGURATION,
                                    wValue: 1, wIndex: 0x0, dataOut: nil)
    end

    wait_and_retrieve_by_serial(sn)
    res == 0
  end
end

#start_accessory_modetrue, false

Switches to accessory mode

Send identifying string information to the device and request the device start up in accessory mode.

Returns:

  • (true, false)

    true for success, false otherwise.



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/ligo/device.rb', line 181

def start_accessory_mode
  logger.debug 'start_accessory_mode'
  sn = self.serial_number

  self.open do |handle|
    @handle = handle
    send_accessory_id
    send_start
    @handle = nil
  end

  wait_and_retrieve_by_serial(sn)
end

#uas?true, false

Check if the current Ligo::Device is in UMS mode

Returns:

  • (true, false)

    true if the Ligo::Device is in UMS mode, false otherwise



242
243
244
245
246
247
248
249
250
# File 'lib/ligo/device.rb', line 242

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

#write(buffer, timeout = 1000) ⇒ Fixnum Also known as: send

Simple write method (blocking until timeout)

Parameters:

  • buffer (String)

    The buffer to be sent.

  • timeout (Fixnum) (defaults to: 1000)

    The timeout in ms (default: 1000). 0 for an infinite timeout.

Returns:

  • (Fixnum)

    the number of bytes actually sent.

Raises:

  • (LIBUSB::ERROR_TIMEOUT)

    in case of timeout.



128
129
130
131
132
# File 'lib/ligo/device.rb', line 128

def write(buffer, timeout = 1000)
    handle.bulk_transfer(endpoint: @out,
                         dataOut: buffer,
                         timeout: timeout)
end