ligo 0.1.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +3 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/LICENSE.txt +202 -0
- data/NOTICE.txt +6 -0
- data/README.md +95 -0
- data/Rakefile +40 -0
- data/bin/ligo-sample +87 -0
- data/lib/ligo/accessory.rb +64 -0
- data/lib/ligo/constants.rb +83 -0
- data/lib/ligo/context.rb +50 -0
- data/lib/ligo/device.rb +300 -0
- data/lib/ligo/logging.rb +54 -0
- data/lib/ligo/version.rb +21 -0
- data/lib/ligo.rb +38 -0
- data/ligo.gemspec +28 -0
- data/spec/ligo/accessory_spec.rb +99 -0
- data/spec/ligo/context_spec.rb +21 -0
- data/spec/ligo/device_spec.rb +45 -0
- data/spec/ligo_spec.rb +7 -0
- data/spec/spec_helper.rb +6 -0
- metadata +155 -0
data/lib/ligo/device.rb
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# -*- coding: utf-8; fill-column: 80 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2012 Renaud AUBIN
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
module Ligo
|
|
19
|
+
|
|
20
|
+
require 'ligo/constants'
|
|
21
|
+
|
|
22
|
+
class Device < LIBUSB::Device
|
|
23
|
+
include Logging
|
|
24
|
+
|
|
25
|
+
# TODO: Document the attr!
|
|
26
|
+
attr_reader :pDev, :pDevDesc
|
|
27
|
+
attr_reader :aoap_version, :accessory, :in, :out, :handle
|
|
28
|
+
|
|
29
|
+
def initialize context, pDev
|
|
30
|
+
@aoap_version = 0
|
|
31
|
+
@accessory, @in, @out, @handle = nil, nil, nil, nil
|
|
32
|
+
super context, pDev
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def process(&block)
|
|
36
|
+
begin
|
|
37
|
+
self.open_interface(0) do |handle|
|
|
38
|
+
@handle = handle
|
|
39
|
+
yield handle
|
|
40
|
+
@handle = nil
|
|
41
|
+
end
|
|
42
|
+
# close
|
|
43
|
+
rescue LIBUSB::ERROR_NO_DEVICE
|
|
44
|
+
msg = 'The target device has been disconnected'
|
|
45
|
+
logger.debug msg
|
|
46
|
+
# close
|
|
47
|
+
raise Interrupt, msg
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def open_and_claim
|
|
52
|
+
@handle = open
|
|
53
|
+
@handle.claim_interface(0)
|
|
54
|
+
@handle.clear_halt(@in)
|
|
55
|
+
@handle
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def finalize
|
|
59
|
+
if @handle
|
|
60
|
+
@handle.release_interface(0)
|
|
61
|
+
@handle.close
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Simple write method (blocking until timeout).
|
|
66
|
+
# @param [Fixnum] buffer_size
|
|
67
|
+
# The number of bytes expected to be received.
|
|
68
|
+
# @param [Fixnum] timeout
|
|
69
|
+
# The timeout in ms (default: 1000). 0 for an infinite timeout.
|
|
70
|
+
# @return [String] the received buffer (at most buffer_size bytes).
|
|
71
|
+
# @raise [LIBUSB::ERROR_TIMEOUT] in case of timeout.
|
|
72
|
+
def read(buffer_size, timeout = 1000)
|
|
73
|
+
handle.bulk_transfer(endpoint: @in,
|
|
74
|
+
dataIn: buffer_size,
|
|
75
|
+
timeout: timeout)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Simple write method (blocking until timeout).
|
|
79
|
+
# @param [String] buffer
|
|
80
|
+
# The buffer to be sent.
|
|
81
|
+
# @param [Fixnum] timeout
|
|
82
|
+
# The timeout in ms (default: 1000). 0 for an infinite timeout.
|
|
83
|
+
# @return [Fixnum] the number of bytes actually sent.
|
|
84
|
+
# @raise [LIBUSB::ERROR_TIMEOUT] in case of timeout.
|
|
85
|
+
def write(buffer, timeout = 1000)
|
|
86
|
+
handle.bulk_transfer(endpoint: @out,
|
|
87
|
+
dataOut: buffer,
|
|
88
|
+
timeout: timeout)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Simple recv method.
|
|
92
|
+
# @param [Fixnum] buffer_size
|
|
93
|
+
# The buffer size of the received buffer.
|
|
94
|
+
# @return [String] the received buffer (at most buffer_size bytes).
|
|
95
|
+
def recv(buffer_size)
|
|
96
|
+
begin
|
|
97
|
+
handle.bulk_transfer(endpoint: @in,
|
|
98
|
+
dataIn: buffer_size)
|
|
99
|
+
rescue LIBUSB::ERROR_TIMEOUT
|
|
100
|
+
nil
|
|
101
|
+
# maybe we should implement a internal thread, a sleep and a retry
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Simple send method.
|
|
106
|
+
# @param [String] data
|
|
107
|
+
# The data to be sent.
|
|
108
|
+
# @return [Fixnum] the number of bytes sent.
|
|
109
|
+
def send(data)
|
|
110
|
+
# TODO: Add timeout param?
|
|
111
|
+
handle.bulk_transfer(endpoint: @out, dataOut: data)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Associate an AOAP compatible device with a virtual accessory and switch the Android device
|
|
115
|
+
# to accessory mode.
|
|
116
|
+
#
|
|
117
|
+
# Prepare an OAP compatible device to interact with a given {Ligo::Accessory}:
|
|
118
|
+
# * Switch the current assigned device to accessory mode
|
|
119
|
+
# * Set the I/O endpoints
|
|
120
|
+
# @param [Ligo::Accessory] accessory
|
|
121
|
+
# The virtual accessory to be associated with the Android device.
|
|
122
|
+
# @return [true, false] true for success, false otherwise.
|
|
123
|
+
def attach_accessory(accessory)
|
|
124
|
+
logger.debug "attach_accessory(#{accessory})"
|
|
125
|
+
|
|
126
|
+
@accessory = accessory
|
|
127
|
+
|
|
128
|
+
if accessory_mode?
|
|
129
|
+
# if the device is already in accessory mode, we send
|
|
130
|
+
# set_configuration to force an usb attached event on the device
|
|
131
|
+
begin
|
|
132
|
+
set_configuration
|
|
133
|
+
rescue LIBUSB::ERROR_NO_DEVICE
|
|
134
|
+
logger.debug ' set_configuration raises LIBUSB::ERROR_NO_DEVICE - Retry'
|
|
135
|
+
sleep REENUMERATION_DELAY
|
|
136
|
+
# Set configuration may fail
|
|
137
|
+
retry
|
|
138
|
+
end
|
|
139
|
+
else
|
|
140
|
+
# the device is not in accessory mode, start_accessory_mode is
|
|
141
|
+
# sufficient to get an usb attached event on the device
|
|
142
|
+
return false unless start_accessory_mode
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Find out the in/out endpoints
|
|
146
|
+
self.interfaces.first.endpoints.each do |ep|
|
|
147
|
+
if ep.bEndpointAddress & 0b10000000 == 0
|
|
148
|
+
@out = ep if @out.nil?
|
|
149
|
+
else
|
|
150
|
+
@in = ep if @in.nil?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Send identifying string information to the device and request the device start up in accessory
|
|
157
|
+
# mode.
|
|
158
|
+
def start_accessory_mode
|
|
159
|
+
logger.debug 'start_accessory_mode'
|
|
160
|
+
sn = self.serial_number
|
|
161
|
+
|
|
162
|
+
self.open_interface(0) do |handle|
|
|
163
|
+
@handle = handle
|
|
164
|
+
send_accessory_id
|
|
165
|
+
send_start
|
|
166
|
+
@handle = nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
wait_and_retrieve_by_serial(sn)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Set the device's configuration to a value of 1 with a SET_CONFIGURATION (0x09) device
|
|
173
|
+
# request.
|
|
174
|
+
# @return [true, false] true for success, false otherwise.
|
|
175
|
+
def set_configuration
|
|
176
|
+
logger.debug 'set_configuration'
|
|
177
|
+
res = nil
|
|
178
|
+
sn = self.serial_number
|
|
179
|
+
device = @context.devices(idVendor: GOOGLE_VID).collect do |d|
|
|
180
|
+
d.serial_number == sn ? d : nil
|
|
181
|
+
end.compact.first
|
|
182
|
+
|
|
183
|
+
begin
|
|
184
|
+
device.open_interface(0) do |handle|
|
|
185
|
+
req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_STANDARD
|
|
186
|
+
res = handle.control_transfer(bmRequestType: req_type,
|
|
187
|
+
bRequest: LIBUSB::REQUEST_SET_CONFIGURATION,
|
|
188
|
+
wValue: 1, wIndex: 0x0, dataOut: nil)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
wait_and_retrieve_by_serial(sn)
|
|
192
|
+
res == 0
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Check if the current {Ligo::Device} is in accessory mode.
|
|
197
|
+
# @return [true, false] true if the {Ligo::Device} is in accessory mode,
|
|
198
|
+
# false otherwise.
|
|
199
|
+
def accessory_mode?
|
|
200
|
+
self.idVendor == GOOGLE_VID
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Check if the current {Ligo::Device} supports AOAP.
|
|
204
|
+
# @return [true, false] true if the {Ligo::Device} supports AOAP, false
|
|
205
|
+
# otherwise.
|
|
206
|
+
def aoap?
|
|
207
|
+
@aoap_version = self.get_protocol
|
|
208
|
+
logger.info "#{self.inspect} supports AOAP version #{@aoap_version}."
|
|
209
|
+
@aoap_version >= 1
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Check if the current {Ligo::Device} is in UMS mode.
|
|
213
|
+
# @return [true, false] true if the {Ligo::Device} is in UMS mode, false
|
|
214
|
+
# otherwise.
|
|
215
|
+
def uas?
|
|
216
|
+
if RUBY_PLATFORM=~/linux/i
|
|
217
|
+
# http://cateee.net/lkddb/web-lkddb/USB_UAS.html
|
|
218
|
+
(self.settings[0].bInterfaceClass == 0x08) &&
|
|
219
|
+
(self.settings[0].bInterfaceSubClass == 0x06)
|
|
220
|
+
else
|
|
221
|
+
false
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Send a 51 control request ("Get Protocol") to figure out if the device
|
|
226
|
+
# supports the Android accessory protocol.
|
|
227
|
+
# @return [Fixnum] the AOAP protocol version supported by the device (0 for
|
|
228
|
+
# no AOAP support).
|
|
229
|
+
def get_protocol
|
|
230
|
+
logger.debug 'get_protocol'
|
|
231
|
+
res, version = 0, 0
|
|
232
|
+
self.open do |h|
|
|
233
|
+
|
|
234
|
+
h.detach_kernel_driver(0) if self.uas? && h.kernel_driver_active?(0)
|
|
235
|
+
req_type = LIBUSB::ENDPOINT_IN | LIBUSB::REQUEST_TYPE_VENDOR
|
|
236
|
+
res = h.control_transfer(bmRequestType: req_type,
|
|
237
|
+
bRequest: COMMAND_GETPROTOCOL,
|
|
238
|
+
wValue: 0x0, wIndex: 0x0, dataIn: 2)
|
|
239
|
+
|
|
240
|
+
version = res.unpack('S')[0]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
(res.size == 2 && version >= 1 ) ? version : 0
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Send identifying string information to the device.
|
|
247
|
+
def send_accessory_id
|
|
248
|
+
logger.debug 'send_accessory_id'
|
|
249
|
+
req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_VENDOR
|
|
250
|
+
@accessory.each do |k,v|
|
|
251
|
+
# Ensure the string is terminated by a null char
|
|
252
|
+
s = "#{v}\0"
|
|
253
|
+
r = @handle.control_transfer(bmRequestType: req_type,
|
|
254
|
+
bRequest: COMMAND_SENDSTRING, wValue: 0x0,
|
|
255
|
+
wIndex: @accessory.keys.index(k), dataOut: s)
|
|
256
|
+
|
|
257
|
+
# TODO: Manage an exception there. This should terminate the program.
|
|
258
|
+
logger.error "Failed to send #{k} string" unless r == s.size
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
private :send_accessory_id
|
|
262
|
+
|
|
263
|
+
# Request the device start up in accessory mode
|
|
264
|
+
def send_start
|
|
265
|
+
logger.debug 'send_start'
|
|
266
|
+
req_type = LIBUSB::ENDPOINT_OUT | LIBUSB::REQUEST_TYPE_VENDOR
|
|
267
|
+
res = @handle.control_transfer(bmRequestType: req_type,
|
|
268
|
+
bRequest: COMMAND_START, wValue: 0x0,
|
|
269
|
+
wIndex: 0x0, dataOut: nil)
|
|
270
|
+
end
|
|
271
|
+
private :send_start
|
|
272
|
+
|
|
273
|
+
# Internal use only.
|
|
274
|
+
def wait_and_retrieve_by_serial(sn)
|
|
275
|
+
sleep REENUMERATION_DELAY
|
|
276
|
+
# The device should now reappear on the usb bus with the Google vendor id.
|
|
277
|
+
# We retrieve it by using its serial number.
|
|
278
|
+
device = @context.devices(idVendor: GOOGLE_VID).collect do |d|
|
|
279
|
+
d.serial_number == sn ? d : nil
|
|
280
|
+
end.compact.first
|
|
281
|
+
|
|
282
|
+
if device
|
|
283
|
+
# Retrieve new pointers (check if the old ones should be dereferenced)
|
|
284
|
+
@pDev = device.pDev
|
|
285
|
+
@pDevDesc = device.pDevDesc
|
|
286
|
+
else
|
|
287
|
+
logger.error ['Failed to retrieve the device after switching to ',
|
|
288
|
+
'accessory mode. This may be due to a lack of proper ',
|
|
289
|
+
'permissions ⇒ check your udev rules.', "\n",
|
|
290
|
+
'The Google vendor id rule may look like:', "\n",
|
|
291
|
+
'SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ',
|
|
292
|
+
'MODE="0666", GROUP="plugdev"'
|
|
293
|
+
].join
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
private :wait_and_retrieve_by_serial
|
|
297
|
+
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
end
|
data/lib/ligo/logging.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8; fill-column: 80 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2012 Renaud AUBIN
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
require 'logger'
|
|
19
|
+
|
|
20
|
+
module Ligo
|
|
21
|
+
|
|
22
|
+
# Logging module.
|
|
23
|
+
#
|
|
24
|
+
# This module enables to share the same logger between all the Ligo classes.
|
|
25
|
+
module Logging
|
|
26
|
+
|
|
27
|
+
def logger
|
|
28
|
+
@logger ||= Logging.logger_for(self.class.name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
|
32
|
+
@loggers = {}
|
|
33
|
+
@out = STDOUT
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def logger_for(classname)
|
|
37
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def configure_logger_for(classname)
|
|
41
|
+
logger = Logger.new(@out)
|
|
42
|
+
logger.progname = classname
|
|
43
|
+
logger
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
def configure_logger_output(logout)
|
|
48
|
+
@out = logout if logout != 'STDOUT'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
data/lib/ligo/version.rb
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8; fill-column: 80 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2012 Renaud AUBIN
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
module Ligo
|
|
19
|
+
# ligō version
|
|
20
|
+
VERSION = "0.1.0.beta"
|
|
21
|
+
end
|
data/lib/ligo.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- coding: utf-8; fill-column: 80 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2012 Renaud AUBIN
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
require 'logger'
|
|
19
|
+
require 'libusb'
|
|
20
|
+
|
|
21
|
+
require 'ligo/version'
|
|
22
|
+
|
|
23
|
+
# Ligo module.
|
|
24
|
+
#
|
|
25
|
+
# This module contains the Android Open Accessory Protocol utility classes to
|
|
26
|
+
# enable custom USB I/O with AOAP-compatible devices.
|
|
27
|
+
#
|
|
28
|
+
# @see http://source.android.com/tech/accessories/aoap/aoa.html
|
|
29
|
+
# @see http://source.android.com/tech/accessories/index.html
|
|
30
|
+
# @see
|
|
31
|
+
# http://developer.android.com/guide/topics/connectivity/usb/accessory.html
|
|
32
|
+
module Ligo
|
|
33
|
+
require 'ligo/logging'
|
|
34
|
+
require 'ligo/constants'
|
|
35
|
+
require 'ligo/accessory'
|
|
36
|
+
require 'ligo/context'
|
|
37
|
+
require 'ligo/device'
|
|
38
|
+
end
|
data/ligo.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require File.expand_path('../lib/ligo/version', __FILE__)
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |gem|
|
|
6
|
+
gem.name = 'ligo'
|
|
7
|
+
gem.version = Ligo::VERSION
|
|
8
|
+
gem.authors = ['Renaud Aubin']
|
|
9
|
+
gem.date = Date.today
|
|
10
|
+
gem.summary = %q{A ruby utility to create virtual accessories able to communicate with Android devices.}
|
|
11
|
+
gem.description = %q{Ligo: virtual accessories for Android}
|
|
12
|
+
gem.license = 'Apache License, Version 2.0'
|
|
13
|
+
gem.authors = ['Renaud AUBIN']
|
|
14
|
+
gem.email = 'root@renaud.io'
|
|
15
|
+
gem.homepage = 'https://github.com/nibua-r/ligo#readme'
|
|
16
|
+
|
|
17
|
+
gem.files = `git ls-files`.split($/)
|
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
20
|
+
gem.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
gem.add_dependency 'libusb', '~> 0.2.2'
|
|
23
|
+
|
|
24
|
+
gem.add_development_dependency 'rspec', '~> 2.4'
|
|
25
|
+
gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
|
26
|
+
gem.add_development_dependency 'yard', '~> 0.8'
|
|
27
|
+
gem.add_development_dependency 'pry', '~> 0.9.10'
|
|
28
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Ligo::Accessory do
|
|
4
|
+
before(:all) do
|
|
5
|
+
@accessory = Ligo::Accessory.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
subject { @accessory }
|
|
9
|
+
it { should respond_to :manufacturer}
|
|
10
|
+
it { should respond_to :model}
|
|
11
|
+
it { should respond_to :description}
|
|
12
|
+
it { should respond_to :version}
|
|
13
|
+
it { should respond_to :uri}
|
|
14
|
+
it { should respond_to :serial}
|
|
15
|
+
|
|
16
|
+
describe '#new' do
|
|
17
|
+
|
|
18
|
+
context 'when called with nil' do
|
|
19
|
+
it 'should raise ArgumentError' do
|
|
20
|
+
expect { Ligo::Accessory.new(nil) }.to raise_error(ArgumentError)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context 'when called with an empty Hash' do
|
|
25
|
+
it 'should raise ArgumentError' do
|
|
26
|
+
expect { Ligo::Accessory.new(Hash.new) }.to raise_error(ArgumentError)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'when called with invalid data (> max length)' do
|
|
31
|
+
before(:all) do
|
|
32
|
+
@accessory_arg = {
|
|
33
|
+
manufacturer: 'a',
|
|
34
|
+
model: 'a',
|
|
35
|
+
description: 'a',
|
|
36
|
+
version: 'a',
|
|
37
|
+
uri: 'a',
|
|
38
|
+
serial: (0...256).map{ ('a'..'z').to_a[rand(26)] }.join
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'should raise ArgumentError' do
|
|
43
|
+
expect do
|
|
44
|
+
Ligo::Accessory.new(@accessory_arg)
|
|
45
|
+
end.to raise_error(ArgumentError,
|
|
46
|
+
'serial must contain at most 255 bytes')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context 'when called with invalid data (wrong datatype)' do
|
|
51
|
+
before(:all) do
|
|
52
|
+
@accessory_arg = {
|
|
53
|
+
manufacturer: 0,
|
|
54
|
+
model: 'a',
|
|
55
|
+
description: 'a',
|
|
56
|
+
version: 'a',
|
|
57
|
+
uri: 'a',
|
|
58
|
+
serial: 'a'
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'should raise ArgumentError' do
|
|
63
|
+
expect do
|
|
64
|
+
Ligo::Accessory.new(@accessory_arg)
|
|
65
|
+
end.to raise_error(ArgumentError,
|
|
66
|
+
'manufacturer is not a String')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
context 'when called with missing data' do
|
|
71
|
+
before(:all) do
|
|
72
|
+
@accessory_arg = {
|
|
73
|
+
manufacturer: 'a',
|
|
74
|
+
model: 'a',
|
|
75
|
+
description: 'a',
|
|
76
|
+
version: 'a',
|
|
77
|
+
serial: 'a'
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'should raise ArgumentError' do
|
|
82
|
+
expect do
|
|
83
|
+
Ligo::Accessory.new(@accessory_arg)
|
|
84
|
+
end.to raise_error(ArgumentError,
|
|
85
|
+
'Missing argument: uri')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end # describe #new
|
|
90
|
+
|
|
91
|
+
describe '#each' do
|
|
92
|
+
it 'must be implemented soon'
|
|
93
|
+
end # describe #each
|
|
94
|
+
|
|
95
|
+
describe '#keys' do
|
|
96
|
+
it 'must be implemented soon'
|
|
97
|
+
end # describe #keys
|
|
98
|
+
|
|
99
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# Assumption: two devices are connected on the usb bus and not yet in accessory
|
|
4
|
+
# mode. Those devices are a Galaxy Nexus running 4.2 AOSP and a HTC Flyer
|
|
5
|
+
# running 3.2 (with UMS support).
|
|
6
|
+
|
|
7
|
+
describe Ligo::Context do
|
|
8
|
+
it "should derive from LIBUSB::Context" do
|
|
9
|
+
subject.class.superclass.should be LIBUSB::Context
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#devices" do
|
|
13
|
+
it "should return an Array" do
|
|
14
|
+
subject.devices.class.should be Array
|
|
15
|
+
end
|
|
16
|
+
it "should return 2 devices" do
|
|
17
|
+
subject.devices.size.should equal 2
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# Assumption: two devices are connected on the usb bus and not yet in accessory
|
|
4
|
+
# mode. Those devices are a Galaxy Nexus running 4.2 AOSP and a HTC Flyer
|
|
5
|
+
# running 3.2 (with UMS support).
|
|
6
|
+
|
|
7
|
+
describe Ligo::Device do
|
|
8
|
+
before(:all) do
|
|
9
|
+
@default_accessory = Ligo::Accessory.new
|
|
10
|
+
ctx = Ligo::Context.new
|
|
11
|
+
@gnexus = ctx.devices(idVendor: 0x04e8).first
|
|
12
|
+
@flyer = ctx.devices(idVendor: 0x0bb4).first
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should derive from LIBUSB::Device' do
|
|
16
|
+
Ligo::Device.superclass.should be LIBUSB::Device
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'when passing the Galaxy Nexus to accessory mode' do
|
|
20
|
+
specify { @gnexus.should_not be_accessory_mode }
|
|
21
|
+
specify { @gnexus.idVendor.should be 0x04e8 }
|
|
22
|
+
specify { @gnexus.idProduct.should be 0x6860 }
|
|
23
|
+
specify { @gnexus.should be_aoap }
|
|
24
|
+
specify { @gnexus.should_not be_uas }
|
|
25
|
+
specify { @gnexus.aoap_version.should be 2 }
|
|
26
|
+
# Now, the order matters!
|
|
27
|
+
specify { @gnexus.attach_accessory(@default_accessory).should be true }
|
|
28
|
+
specify { @gnexus.idVendor.should be 0x18d1 }
|
|
29
|
+
specify { Ligo::GOOGLE_PIDS.should include @gnexus.idProduct }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'when passing the Flyer to accessory mode' do
|
|
33
|
+
specify { @flyer.should_not be_accessory_mode }
|
|
34
|
+
specify { @flyer.idVendor.should be 0x0bb4 }
|
|
35
|
+
specify { @flyer.idProduct.should be 0x0ca9 }
|
|
36
|
+
specify { @flyer.should be_aoap }
|
|
37
|
+
specify { @flyer.should be_uas }
|
|
38
|
+
specify { @flyer.aoap_version.should be 1 }
|
|
39
|
+
# Now, the order matters!
|
|
40
|
+
specify { @flyer.attach_accessory(@default_accessory).should be true }
|
|
41
|
+
specify { @flyer.idVendor.should be 0x18d1 }
|
|
42
|
+
specify { Ligo::GOOGLE_PIDS.should include @flyer.idProduct }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
data/spec/ligo_spec.rb
ADDED