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.



76
77
78
79
80
# File 'lib/ligo/device.rb', line 76

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.



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

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.



54
55
56
# File 'lib/ligo/device.rb', line 54

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.



73
74
75
# File 'lib/ligo/device.rb', line 73

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.



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

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.



68
69
70
# File 'lib/ligo/device.rb', line 68

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.



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

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.



49
50
51
# File 'lib/ligo/device.rb', line 49

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.



233
234
235
# File 'lib/ligo/device.rb', line 233

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.



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

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.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ligo/device.rb', line 154

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 Ligo::getDelay
      # 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.



111
112
113
114
115
116
# File 'lib/ligo/device.rb', line 111

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.



327
328
329
330
331
# File 'lib/ligo/device.rb', line 327

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).



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/ligo/device.rb', line 270

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:



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

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

#process(&block) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ligo/device.rb', line 82

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.



125
126
127
128
129
# File 'lib/ligo/device.rb', line 125

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.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/ligo/device.rb', line 211

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.



192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ligo/device.rb', line 192

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



253
254
255
256
257
258
259
260
261
# File 'lib/ligo/device.rb', line 253

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.



139
140
141
142
143
# File 'lib/ligo/device.rb', line 139

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