tenderlove-usb 0.3.0

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.
@@ -0,0 +1,422 @@
1
+ # usb.rb - utility methods for libusb binding for Ruby.
2
+ #
3
+ # Copyright (C) 2007 Tanaka Akira
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require 'usb.so'
20
+
21
+ # USB module is a binding for libusb.
22
+ #
23
+ # It needs appropriate privilege to access USB.
24
+ # For example, the process should be an member of plugdev group on Debin GNU/Linux (etch).
25
+ #
26
+ # = Example
27
+ #
28
+ # 1. list up USB devices
29
+ #
30
+ # require 'usb'
31
+ # require 'pp'
32
+ # pp USB.devices
33
+ # #=>
34
+ # [#<USB::Device 001/001 0000:0000 Linux 2.6.17-2-486 uhci_hcd UHCI Host Controller 0000:00:1d.0 (Full speed Hub)>,
35
+ # #<USB::Device 002/001 0000:0000 Linux 2.6.17-2-486 uhci_hcd UHCI Host Controller 0000:00:1d.1 (Full speed Hub)>,
36
+ # #<USB::Device 003/001 0000:0000 Linux 2.6.17-2-486 uhci_hcd UHCI Host Controller 0000:00:1d.2 (Full speed Hub)>,
37
+ # #<USB::Device 004/001 0000:0000 Linux 2.6.17-2-486 ehci_hcd EHCI Host Controller 0000:00:1d.7 (Hi-speed Hub with single TT)>]
38
+ #
39
+ # 2. find a device by bus id and device id
40
+ #
41
+ # # find the device "004/001" in the above list.
42
+ # dev = USB.find_bus(4).find_device(1)
43
+ # p dev
44
+ # #=>
45
+ # #<USB::Device 004/001 0000:0000 Linux 2.6.17-2-486 ehci_hcd EHCI Host Controller 0000:00:1d.7 (Hi-speed Hub with single TT)>
46
+ #
47
+ # 3. open a device
48
+ #
49
+ # dev.open {|handle| p handle }
50
+ # #=>
51
+ # #<USB::DevHandle:0xa7d94688>
52
+ #
53
+ # = USB overview
54
+ #
55
+ # * A host has busses.
56
+ # * A bus has devices.
57
+ # * A device has configurations.
58
+ # * A configuration has interfaces.
59
+ # * A interface has settings.
60
+ # * A setting has endpoints.
61
+ #
62
+
63
+ module USB
64
+ VERSION = '0.3.0'
65
+
66
+ def USB.busses
67
+ result = []
68
+ bus = USB.first_bus
69
+ while bus
70
+ result << bus
71
+ bus = bus.next
72
+ end
73
+ result.sort_by {|b| b.dirname }
74
+ end
75
+
76
+ def USB.devices() USB.busses.map {|b| b.devices }.flatten end
77
+ def USB.configurations() USB.devices.map {|d| d.configurations }.flatten end
78
+ def USB.interfaces() USB.configurations.map {|d| d.interfaces }.flatten end
79
+ def USB.settings() USB.interfaces.map {|d| d.settings }.flatten end
80
+ def USB.endpoints() USB.settings.map {|d| d.endpoints }.flatten end
81
+
82
+ def USB.find_bus(n)
83
+ bus = USB.first_bus
84
+ while bus
85
+ return bus if n == bus.dirname.to_i
86
+ bus = bus.next
87
+ end
88
+ return nil
89
+ end
90
+
91
+ # searches devices by USB device class, subclass and protocol.
92
+ #
93
+ # # find hubs.
94
+ # USB.each_device_by_class(USB::USB_CLASS_HUB) {|d| p d }'
95
+ #
96
+ # # find Full speed Hubs
97
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 0) {|d| p d }'
98
+ #
99
+ # # find Hi-speed Hubs with single TT
100
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 1) {|d| p d }'
101
+ #
102
+ # # find Hi-speed Hubs with multiple TT
103
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 2) {|d| p d }'
104
+ #
105
+ def USB.each_device_by_class(devclass, subclass=nil, protocol=nil)
106
+ USB.devices.each {|dev|
107
+ if dev.bDeviceClass == USB::USB_CLASS_PER_INTERFACE
108
+ found = dev.settings.any? {|s|
109
+ s.bInterfaceClass == devclass &&
110
+ (!subclass || s.bInterfaceSubClass == subclass) &&
111
+ (!protocol || s.bInterfaceProtocol == protocol) }
112
+ else
113
+ found = dev.bDeviceClass == devclass &&
114
+ (!subclass || dev.bDeviceSubClass == subclass) &&
115
+ (!protocol || dev.bDeviceProtocol == protocol)
116
+ end
117
+ yield dev if found
118
+ }
119
+ nil
120
+ end
121
+
122
+ class Bus
123
+ def inspect
124
+ if self.revoked?
125
+ "\#<#{self.class} revoked>"
126
+ else
127
+ "\#<#{self.class} #{self.dirname}>"
128
+ end
129
+ end
130
+
131
+ def devices
132
+ result = []
133
+ device = self.first_device
134
+ while device
135
+ result << device
136
+ device = device.next
137
+ end
138
+ result.sort_by {|d| d.filename }
139
+ end
140
+
141
+ def configurations() self.devices.map {|d| d.configurations }.flatten end
142
+ def interfaces() self.configurations.map {|d| d.interfaces }.flatten end
143
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
144
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
145
+
146
+ def find_device(n)
147
+ device = self.first_device
148
+ while device
149
+ return device if n == device.filename.to_i
150
+ device = device.next
151
+ end
152
+ return nil
153
+ end
154
+ end
155
+
156
+ # :stopdoc:
157
+ # http://www.usb.org/developers/defined_class
158
+ CLASS_CODES = [
159
+ [0x01, nil, nil, "Audio"],
160
+ [0x02, nil, nil, "Comm"],
161
+ [0x03, nil, nil, "HID"],
162
+ [0x05, nil, nil, "Physical"],
163
+ [0x06, 0x01, 0x01, "StillImaging"],
164
+ [0x06, nil, nil, "Image"],
165
+ [0x07, nil, nil, "Printer"],
166
+ [0x08, 0x01, nil, "MassStorage RBC Bluk-Only"],
167
+ [0x08, 0x02, 0x50, "MassStorage ATAPI Bluk-Only"],
168
+ [0x08, 0x03, 0x50, "MassStorage QIC-157 Bluk-Only"],
169
+ [0x08, 0x04, nil, "MassStorage UFI"],
170
+ [0x08, 0x05, 0x50, "MassStorage SFF-8070i Bluk-Only"],
171
+ [0x08, 0x06, 0x50, "MassStorage SCSI Bluk-Only"],
172
+ [0x08, nil, nil, "MassStorage"],
173
+ [0x09, 0x00, 0x00, "Full speed Hub"],
174
+ [0x09, 0x00, 0x01, "Hi-speed Hub with single TT"],
175
+ [0x09, 0x00, 0x02, "Hi-speed Hub with multiple TTs"],
176
+ [0x09, nil, nil, "Hub"],
177
+ [0x0a, nil, nil, "CDC"],
178
+ [0x0b, nil, nil, "SmartCard"],
179
+ [0x0d, 0x00, 0x00, "ContentSecurity"],
180
+ [0x0e, nil, nil, "Video"],
181
+ [0xdc, 0x01, 0x01, "Diagnostic USB2"],
182
+ [0xdc, nil, nil, "Diagnostic"],
183
+ [0xe0, 0x01, 0x01, "Bluetooth"],
184
+ [0xe0, 0x01, 0x02, "UWB"],
185
+ [0xe0, 0x01, 0x03, "RemoteNDIS"],
186
+ [0xe0, 0x02, 0x01, "Host Wire Adapter Control/Data"],
187
+ [0xe0, 0x02, 0x02, "Device Wire Adapter Control/Data"],
188
+ [0xe0, 0x02, 0x03, "Device Wire Adapter Isochronous"],
189
+ [0xe0, nil, nil, "Wireless Controller"],
190
+ [0xef, 0x01, 0x01, "Active Sync"],
191
+ [0xef, 0x01, 0x02, "Palm Sync"],
192
+ [0xef, 0x02, 0x01, "Interface Association Descriptor"],
193
+ [0xef, 0x02, 0x02, "Wire Adapter Multifunction Peripheral"],
194
+ [0xef, 0x03, 0x01, "Cable Based Association Framework"],
195
+ [0xef, nil, nil, "Miscellaneous"],
196
+ [0xfe, 0x01, 0x01, "Device Firmware Upgrade"],
197
+ [0xfe, 0x02, 0x00, "IRDA Bridge"],
198
+ [0xfe, 0x03, 0x00, "USB Test and Measurement"],
199
+ [0xfe, 0x03, 0x01, "USB Test and Measurement (USBTMC USB488)"],
200
+ [0xfe, nil, nil, "Application Specific"],
201
+ [0xff, nil, nil, "Vendor specific"],
202
+ ]
203
+ CLASS_CODES_HASH1 = {}
204
+ CLASS_CODES_HASH2 = {}
205
+ CLASS_CODES_HASH3 = {}
206
+ CLASS_CODES.each {|base_class, sub_class, protocol, desc|
207
+ if protocol
208
+ CLASS_CODES_HASH3[[base_class, sub_class, protocol]] = desc
209
+ elsif sub_class
210
+ CLASS_CODES_HASH2[[base_class, sub_class]] = desc
211
+ else
212
+ CLASS_CODES_HASH1[base_class] = desc
213
+ end
214
+ }
215
+
216
+ def USB.dev_string(base_class, sub_class, protocol)
217
+ if desc = CLASS_CODES_HASH3[[base_class, sub_class, protocol]]
218
+ desc
219
+ elsif desc = CLASS_CODES_HASH2[[base_class, sub_class]]
220
+ desc + " (%02x)" % [protocol]
221
+ elsif desc = CLASS_CODES_HASH1[base_class]
222
+ desc + " (%02x,%02x)" % [sub_class, protocol]
223
+ else
224
+ "Unkonwn(%02x,%02x,%02x)" % [base_class, sub_class, protocol]
225
+ end
226
+ end
227
+ # :startdoc:
228
+
229
+ class Device
230
+ def inspect
231
+ if self.revoked?
232
+ "\#<#{self.class} revoked>"
233
+ else
234
+ attrs = []
235
+ attrs << "#{self.bus.dirname}/#{self.filename}"
236
+ attrs << ("%04x:%04x" % [self.idVendor, self.idProduct])
237
+ attrs << self.manufacturer
238
+ attrs << self.product
239
+ attrs << self.serial_number
240
+ if self.bDeviceClass == USB::USB_CLASS_PER_INTERFACE
241
+ devclass = self.settings.map {|i|
242
+ USB.dev_string(i.bInterfaceClass, i.bInterfaceSubClass, i.bInterfaceProtocol)
243
+ }.join(", ")
244
+ else
245
+ devclass = USB.dev_string(self.bDeviceClass, self.bDeviceSubClass, self.bDeviceProtocol)
246
+ end
247
+ attrs << "(#{devclass})"
248
+ attrs.compact!
249
+ "\#<#{self.class} #{attrs.join(' ')}>"
250
+ end
251
+ end
252
+
253
+ def manufacturer
254
+ return @manufacturer if defined? @manufacturer
255
+ @manufacturer = self.open {|h| h.get_string_simple(self.iManufacturer) }
256
+ @manufacturer.strip! if @manufacturer
257
+ @manufacturer
258
+ end
259
+
260
+ def product
261
+ return @product if defined? @product
262
+ @product = self.open {|h| h.get_string_simple(self.iProduct) }
263
+ @product.strip! if @product
264
+ @product
265
+ end
266
+
267
+ def serial_number
268
+ return @serial_number if defined? @serial_number
269
+ @serial_number = self.open {|h| h.get_string_simple(self.iSerialNumber) }
270
+ @serial_number.strip! if @serial_number
271
+ @serial_number
272
+ end
273
+
274
+ def open
275
+ h = self.usb_open
276
+ if block_given?
277
+ begin
278
+ r = yield h
279
+ ensure
280
+ h.usb_close
281
+ end
282
+ else
283
+ h
284
+ end
285
+ end
286
+
287
+ def interfaces() self.configurations.map {|d| d.interfaces }.flatten end
288
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
289
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
290
+ end
291
+
292
+ class Configuration
293
+ def inspect
294
+ if self.revoked?
295
+ "\#<#{self.class} revoked>"
296
+ else
297
+ attrs = []
298
+ attrs << self.bConfigurationValue.to_s
299
+ bits = self.bmAttributes
300
+ attrs << "SelfPowered" if (bits & 0b1000000) != 0
301
+ attrs << "RemoteWakeup" if (bits & 0b100000) != 0
302
+ desc = self.description
303
+ attrs << desc if desc != '?'
304
+ "\#<#{self.class} #{attrs.join(' ')}>"
305
+ end
306
+ end
307
+
308
+ def description
309
+ return @description if defined? @description
310
+ @description = self.device.open {|h| h.get_string_simple(self.iConfiguration) }
311
+ end
312
+
313
+ def bus() self.device.bus end
314
+
315
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
316
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
317
+ end
318
+
319
+ class Interface
320
+ def inspect
321
+ if self.revoked?
322
+ "\#<#{self.class} revoked>"
323
+ else
324
+ "\#<#{self.class}>"
325
+ end
326
+ end
327
+
328
+ def bus() self.configuration.device.bus end
329
+ def device() self.configuration.device end
330
+
331
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
332
+ end
333
+
334
+ class Setting
335
+ def inspect
336
+ if self.revoked?
337
+ "\#<#{self.class} revoked>"
338
+ else
339
+ attrs = []
340
+ attrs << self.bAlternateSetting.to_s
341
+ devclass = USB.dev_string(self.bInterfaceClass, self.bInterfaceSubClass, self.bInterfaceProtocol)
342
+ attrs << devclass
343
+ desc = self.description
344
+ attrs << desc if desc != '?'
345
+ "\#<#{self.class} #{attrs.join(' ')}>"
346
+ end
347
+ end
348
+
349
+ def description
350
+ return @description if defined? @description
351
+ @description = self.device.open {|h| h.get_string_simple(self.iInterface) }
352
+ end
353
+
354
+ def bus() self.interface.configuration.device.bus end
355
+ def device() self.interface.configuration.device end
356
+ def configuration() self.interface.configuration end
357
+ end
358
+
359
+ class Endpoint
360
+ def inspect
361
+ if self.revoked?
362
+ "\#<#{self.class} revoked>"
363
+ else
364
+ endpoint_address = self.bEndpointAddress
365
+ num = endpoint_address & 0b00001111
366
+ inout = (endpoint_address & 0b10000000) == 0 ? "OUT" : "IN "
367
+ bits = self.bmAttributes
368
+ transfer_type = %w[Control Isochronous Bulk Interrupt][0b11 & bits]
369
+ type = [transfer_type]
370
+ if transfer_type == 'Isochronous'
371
+ synchronization_type = %w[NoSynchronization Asynchronous Adaptive Synchronous][(0b1100 & bits) >> 2]
372
+ usage_type = %w[Data Feedback ImplicitFeedback ?][(0b110000 & bits) >> 4]
373
+ type << synchronization_type << usage_type
374
+ end
375
+ "\#<#{self.class} #{num} #{inout} #{type.join(" ")}>"
376
+ end
377
+ end
378
+
379
+ def bus() self.setting.interface.configuration.device.bus end
380
+ def device() self.setting.interface.configuration.device end
381
+ def configuration() self.setting.interface.configuration end
382
+ def interface() self.setting.interface end
383
+ end
384
+
385
+ class DevHandle
386
+ def set_configuration(configuration)
387
+ configuration = configuration.bConfigurationValue if configuration.respond_to? :bConfigurationValue
388
+ self.usb_set_configuration(configuration)
389
+ end
390
+
391
+ def set_altinterface(alternate)
392
+ alternate = alternate.bAlternateSetting if alternate.respond_to? :bAlternateSetting
393
+ self.usb_set_altinterface(alternate)
394
+ end
395
+
396
+ def clear_halt(ep)
397
+ ep = ep.bEndpointAddress if ep.respond_to? :bEndpointAddress
398
+ self.usb_clear_halt(ep)
399
+ end
400
+
401
+ def claim_interface(interface)
402
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
403
+ self.usb_claim_interface(interface)
404
+ end
405
+
406
+ def release_interface(interface)
407
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
408
+ self.usb_release_interface(interface)
409
+ end
410
+
411
+ def get_string_simple(index)
412
+ result = "\0" * 1024
413
+ begin
414
+ self.usb_get_string_simple(index, result)
415
+ rescue Errno::EPIPE, Errno::EFBIG, Errno::EPERM
416
+ return nil
417
+ end
418
+ result.delete!("\0")
419
+ result
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # usage: usb-power bus/device port on|off
4
+ #
5
+ # example:
6
+ # usb-power 004/006 2 on
7
+ # usb-power 004/006 2 off
8
+
9
+ require 'usb'
10
+ require 'optparse'
11
+
12
+ USB_RT_PORT = USB::USB_TYPE_CLASS | USB::USB_RECIP_OTHER
13
+ USB_PORT_FEAT_POWER = 8
14
+
15
+ def list_usb2_hub
16
+ USB.devices.find_all {|d|
17
+ 0x200 <= d.bcdDevice &&
18
+ d.bDeviceClass == USB::USB_CLASS_HUB
19
+ }
20
+ end
21
+
22
+ require 'pp'
23
+
24
+ def power_on(h, port)
25
+ h.usb_control_msg(USB_RT_PORT, USB::USB_REQ_SET_FEATURE, USB_PORT_FEAT_POWER, port, "", 0)
26
+ end
27
+
28
+ def power_off(h, port)
29
+ h.usb_control_msg(USB_RT_PORT, USB::USB_REQ_CLEAR_FEATURE, USB_PORT_FEAT_POWER, port, "", 0)
30
+ end
31
+
32
+ bus_device = ARGV.shift
33
+ port = ARGV.shift.to_i
34
+ on_off = ARGV.shift
35
+
36
+ %r{/} =~ bus_device
37
+ bus = $`.to_i
38
+ device = $'.to_i
39
+
40
+ USB.find_bus(bus).find_device(device).open {|h|
41
+ if on_off == 'off'
42
+ power_off(h, port)
43
+ else
44
+ power_on(h, port)
45
+ end
46
+ }