ruby-usb 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/usb.rb ADDED
@@ -0,0 +1,420 @@
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
+ def USB.busses
65
+ result = []
66
+ bus = USB.first_bus
67
+ while bus
68
+ result << bus
69
+ bus = bus.next
70
+ end
71
+ result.sort_by {|b| b.dirname }
72
+ end
73
+
74
+ def USB.devices() USB.busses.map {|b| b.devices }.flatten end
75
+ def USB.configurations() USB.devices.map {|d| d.configurations }.flatten end
76
+ def USB.interfaces() USB.configurations.map {|d| d.interfaces }.flatten end
77
+ def USB.settings() USB.interfaces.map {|d| d.settings }.flatten end
78
+ def USB.endpoints() USB.settings.map {|d| d.endpoints }.flatten end
79
+
80
+ def USB.find_bus(n)
81
+ bus = USB.first_bus
82
+ while bus
83
+ return bus if n == bus.dirname.to_i
84
+ bus = bus.next
85
+ end
86
+ return nil
87
+ end
88
+
89
+ # searches devices by USB device class, subclass and protocol.
90
+ #
91
+ # # find hubs.
92
+ # USB.each_device_by_class(USB::USB_CLASS_HUB) {|d| p d }'
93
+ #
94
+ # # find Full speed Hubs
95
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 0) {|d| p d }'
96
+ #
97
+ # # find Hi-speed Hubs with single TT
98
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 1) {|d| p d }'
99
+ #
100
+ # # find Hi-speed Hubs with multiple TT
101
+ # USB.each_device_by_class(USB::USB_CLASS_HUB, 0, 2) {|d| p d }'
102
+ #
103
+ def USB.each_device_by_class(devclass, subclass=nil, protocol=nil)
104
+ USB.devices.each {|dev|
105
+ if dev.bDeviceClass == USB::USB_CLASS_PER_INTERFACE
106
+ found = dev.settings.any? {|s|
107
+ s.bInterfaceClass == devclass &&
108
+ (!subclass || s.bInterfaceSubClass == subclass) &&
109
+ (!protocol || s.bInterfaceProtocol == protocol) }
110
+ else
111
+ found = dev.bDeviceClass == devclass &&
112
+ (!subclass || dev.bDeviceSubClass == subclass) &&
113
+ (!protocol || dev.bDeviceProtocol == protocol)
114
+ end
115
+ yield dev if found
116
+ }
117
+ nil
118
+ end
119
+
120
+ class Bus
121
+ def inspect
122
+ if self.revoked?
123
+ "\#<#{self.class} revoked>"
124
+ else
125
+ "\#<#{self.class} #{self.dirname}>"
126
+ end
127
+ end
128
+
129
+ def devices
130
+ result = []
131
+ device = self.first_device
132
+ while device
133
+ result << device
134
+ device = device.next
135
+ end
136
+ result.sort_by {|d| d.filename }
137
+ end
138
+
139
+ def configurations() self.devices.map {|d| d.configurations }.flatten end
140
+ def interfaces() self.configurations.map {|d| d.interfaces }.flatten end
141
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
142
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
143
+
144
+ def find_device(n)
145
+ device = self.first_device
146
+ while device
147
+ return device if n == device.filename.to_i
148
+ device = device.next
149
+ end
150
+ return nil
151
+ end
152
+ end
153
+
154
+ # :stopdoc:
155
+ # http://www.usb.org/developers/defined_class
156
+ CLASS_CODES = [
157
+ [0x01, nil, nil, "Audio"],
158
+ [0x02, nil, nil, "Comm"],
159
+ [0x03, nil, nil, "HID"],
160
+ [0x05, nil, nil, "Physical"],
161
+ [0x06, 0x01, 0x01, "StillImaging"],
162
+ [0x06, nil, nil, "Image"],
163
+ [0x07, nil, nil, "Printer"],
164
+ [0x08, 0x01, nil, "MassStorage RBC Bluk-Only"],
165
+ [0x08, 0x02, 0x50, "MassStorage ATAPI Bluk-Only"],
166
+ [0x08, 0x03, 0x50, "MassStorage QIC-157 Bluk-Only"],
167
+ [0x08, 0x04, nil, "MassStorage UFI"],
168
+ [0x08, 0x05, 0x50, "MassStorage SFF-8070i Bluk-Only"],
169
+ [0x08, 0x06, 0x50, "MassStorage SCSI Bluk-Only"],
170
+ [0x08, nil, nil, "MassStorage"],
171
+ [0x09, 0x00, 0x00, "Full speed Hub"],
172
+ [0x09, 0x00, 0x01, "Hi-speed Hub with single TT"],
173
+ [0x09, 0x00, 0x02, "Hi-speed Hub with multiple TTs"],
174
+ [0x09, nil, nil, "Hub"],
175
+ [0x0a, nil, nil, "CDC"],
176
+ [0x0b, nil, nil, "SmartCard"],
177
+ [0x0d, 0x00, 0x00, "ContentSecurity"],
178
+ [0x0e, nil, nil, "Video"],
179
+ [0xdc, 0x01, 0x01, "Diagnostic USB2"],
180
+ [0xdc, nil, nil, "Diagnostic"],
181
+ [0xe0, 0x01, 0x01, "Bluetooth"],
182
+ [0xe0, 0x01, 0x02, "UWB"],
183
+ [0xe0, 0x01, 0x03, "RemoteNDIS"],
184
+ [0xe0, 0x02, 0x01, "Host Wire Adapter Control/Data"],
185
+ [0xe0, 0x02, 0x02, "Device Wire Adapter Control/Data"],
186
+ [0xe0, 0x02, 0x03, "Device Wire Adapter Isochronous"],
187
+ [0xe0, nil, nil, "Wireless Controller"],
188
+ [0xef, 0x01, 0x01, "Active Sync"],
189
+ [0xef, 0x01, 0x02, "Palm Sync"],
190
+ [0xef, 0x02, 0x01, "Interface Association Descriptor"],
191
+ [0xef, 0x02, 0x02, "Wire Adapter Multifunction Peripheral"],
192
+ [0xef, 0x03, 0x01, "Cable Based Association Framework"],
193
+ [0xef, nil, nil, "Miscellaneous"],
194
+ [0xfe, 0x01, 0x01, "Device Firmware Upgrade"],
195
+ [0xfe, 0x02, 0x00, "IRDA Bridge"],
196
+ [0xfe, 0x03, 0x00, "USB Test and Measurement"],
197
+ [0xfe, 0x03, 0x01, "USB Test and Measurement (USBTMC USB488)"],
198
+ [0xfe, nil, nil, "Application Specific"],
199
+ [0xff, nil, nil, "Vendor specific"],
200
+ ]
201
+ CLASS_CODES_HASH1 = {}
202
+ CLASS_CODES_HASH2 = {}
203
+ CLASS_CODES_HASH3 = {}
204
+ CLASS_CODES.each {|base_class, sub_class, protocol, desc|
205
+ if protocol
206
+ CLASS_CODES_HASH3[[base_class, sub_class, protocol]] = desc
207
+ elsif sub_class
208
+ CLASS_CODES_HASH2[[base_class, sub_class]] = desc
209
+ else
210
+ CLASS_CODES_HASH1[base_class] = desc
211
+ end
212
+ }
213
+
214
+ def USB.dev_string(base_class, sub_class, protocol)
215
+ if desc = CLASS_CODES_HASH3[[base_class, sub_class, protocol]]
216
+ desc
217
+ elsif desc = CLASS_CODES_HASH2[[base_class, sub_class]]
218
+ desc + " (%02x)" % [protocol]
219
+ elsif desc = CLASS_CODES_HASH1[base_class]
220
+ desc + " (%02x,%02x)" % [sub_class, protocol]
221
+ else
222
+ "Unkonwn(%02x,%02x,%02x)" % [base_class, sub_class, protocol]
223
+ end
224
+ end
225
+ # :startdoc:
226
+
227
+ class Device
228
+ def inspect
229
+ if self.revoked?
230
+ "\#<#{self.class} revoked>"
231
+ else
232
+ attrs = []
233
+ attrs << "#{self.bus.dirname}/#{self.filename}"
234
+ attrs << ("%04x:%04x" % [self.idVendor, self.idProduct])
235
+ attrs << self.manufacturer
236
+ attrs << self.product
237
+ attrs << self.serial_number
238
+ if self.bDeviceClass == USB::USB_CLASS_PER_INTERFACE
239
+ devclass = self.settings.map {|i|
240
+ USB.dev_string(i.bInterfaceClass, i.bInterfaceSubClass, i.bInterfaceProtocol)
241
+ }.join(", ")
242
+ else
243
+ devclass = USB.dev_string(self.bDeviceClass, self.bDeviceSubClass, self.bDeviceProtocol)
244
+ end
245
+ attrs << "(#{devclass})"
246
+ attrs.compact!
247
+ "\#<#{self.class} #{attrs.join(' ')}>"
248
+ end
249
+ end
250
+
251
+ def manufacturer
252
+ return @manufacturer if defined? @manufacturer
253
+ @manufacturer = self.open {|h| h.get_string_simple(self.iManufacturer) }
254
+ @manufacturer.strip! if @manufacturer
255
+ @manufacturer
256
+ end
257
+
258
+ def product
259
+ return @product if defined? @product
260
+ @product = self.open {|h| h.get_string_simple(self.iProduct) }
261
+ @product.strip! if @product
262
+ @product
263
+ end
264
+
265
+ def serial_number
266
+ return @serial_number if defined? @serial_number
267
+ @serial_number = self.open {|h| h.get_string_simple(self.iSerialNumber) }
268
+ @serial_number.strip! if @serial_number
269
+ @serial_number
270
+ end
271
+
272
+ def open
273
+ h = self.usb_open
274
+ if block_given?
275
+ begin
276
+ r = yield h
277
+ ensure
278
+ h.usb_close
279
+ end
280
+ else
281
+ h
282
+ end
283
+ end
284
+
285
+ def interfaces() self.configurations.map {|d| d.interfaces }.flatten end
286
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
287
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
288
+ end
289
+
290
+ class Configuration
291
+ def inspect
292
+ if self.revoked?
293
+ "\#<#{self.class} revoked>"
294
+ else
295
+ attrs = []
296
+ attrs << self.bConfigurationValue.to_s
297
+ bits = self.bmAttributes
298
+ attrs << "SelfPowered" if (bits & 0b1000000) != 0
299
+ attrs << "RemoteWakeup" if (bits & 0b100000) != 0
300
+ desc = self.description
301
+ attrs << desc if desc != '?'
302
+ "\#<#{self.class} #{attrs.join(' ')}>"
303
+ end
304
+ end
305
+
306
+ def description
307
+ return @description if defined? @description
308
+ @description = self.device.open {|h| h.get_string_simple(self.iConfiguration) }
309
+ end
310
+
311
+ def bus() self.device.bus end
312
+
313
+ def settings() self.interfaces.map {|d| d.settings }.flatten end
314
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
315
+ end
316
+
317
+ class Interface
318
+ def inspect
319
+ if self.revoked?
320
+ "\#<#{self.class} revoked>"
321
+ else
322
+ "\#<#{self.class}>"
323
+ end
324
+ end
325
+
326
+ def bus() self.configuration.device.bus end
327
+ def device() self.configuration.device end
328
+
329
+ def endpoints() self.settings.map {|d| d.endpoints }.flatten end
330
+ end
331
+
332
+ class Setting
333
+ def inspect
334
+ if self.revoked?
335
+ "\#<#{self.class} revoked>"
336
+ else
337
+ attrs = []
338
+ attrs << self.bAlternateSetting.to_s
339
+ devclass = USB.dev_string(self.bInterfaceClass, self.bInterfaceSubClass, self.bInterfaceProtocol)
340
+ attrs << devclass
341
+ desc = self.description
342
+ attrs << desc if desc != '?'
343
+ "\#<#{self.class} #{attrs.join(' ')}>"
344
+ end
345
+ end
346
+
347
+ def description
348
+ return @description if defined? @description
349
+ @description = self.device.open {|h| h.get_string_simple(self.iInterface) }
350
+ end
351
+
352
+ def bus() self.interface.configuration.device.bus end
353
+ def device() self.interface.configuration.device end
354
+ def configuration() self.interface.configuration end
355
+ end
356
+
357
+ class Endpoint
358
+ def inspect
359
+ if self.revoked?
360
+ "\#<#{self.class} revoked>"
361
+ else
362
+ endpoint_address = self.bEndpointAddress
363
+ num = endpoint_address & 0b00001111
364
+ inout = (endpoint_address & 0b10000000) == 0 ? "OUT" : "IN "
365
+ bits = self.bmAttributes
366
+ transfer_type = %w[Control Isochronous Bulk Interrupt][0b11 & bits]
367
+ type = [transfer_type]
368
+ if transfer_type == 'Isochronous'
369
+ synchronization_type = %w[NoSynchronization Asynchronous Adaptive Synchronous][(0b1100 & bits) >> 2]
370
+ usage_type = %w[Data Feedback ImplicitFeedback ?][(0b110000 & bits) >> 4]
371
+ type << synchronization_type << usage_type
372
+ end
373
+ "\#<#{self.class} #{num} #{inout} #{type.join(" ")}>"
374
+ end
375
+ end
376
+
377
+ def bus() self.setting.interface.configuration.device.bus end
378
+ def device() self.setting.interface.configuration.device end
379
+ def configuration() self.setting.interface.configuration end
380
+ def interface() self.setting.interface end
381
+ end
382
+
383
+ class DevHandle
384
+ def set_configuration(configuration)
385
+ configuration = configuration.bConfigurationValue if configuration.respond_to? :bConfigurationValue
386
+ self.usb_set_configuration(configuration)
387
+ end
388
+
389
+ def set_altinterface(alternate)
390
+ alternate = alternate.bAlternateSetting if alternate.respond_to? :bAlternateSetting
391
+ self.usb_set_altinterface(alternate)
392
+ end
393
+
394
+ def clear_halt(ep)
395
+ ep = ep.bEndpointAddress if ep.respond_to? :bEndpointAddress
396
+ self.usb_clear_halt(ep)
397
+ end
398
+
399
+ def claim_interface(interface)
400
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
401
+ self.usb_claim_interface(interface)
402
+ end
403
+
404
+ def release_interface(interface)
405
+ interface = interface.bInterfaceNumber if interface.respond_to? :bInterfaceNumber
406
+ self.usb_release_interface(interface)
407
+ end
408
+
409
+ def get_string_simple(index)
410
+ result = "\0" * 1024
411
+ begin
412
+ self.usb_get_string_simple(index, result)
413
+ rescue Errno::EPIPE, Errno::EFBIG, Errno::EPERM
414
+ return nil
415
+ end
416
+ result.delete!("\0")
417
+ result
418
+ end
419
+ end
420
+ end
data/sample/usb-power ADDED
@@ -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
+ }