0
# USB Descriptors
1
2
USB descriptors provide structured access to device configuration information including configurations, interfaces, alternate settings, and endpoints. These read-only objects mirror the USB descriptor hierarchy and allow applications to understand device capabilities and requirements.
3
4
## Capabilities
5
6
### Configuration Descriptors
7
8
Configuration descriptors represent complete device configurations containing all interfaces and their settings.
9
10
```python { .api }
11
class USBConfiguration:
12
def getNumInterfaces(self):
13
"""
14
Get number of interfaces in this configuration.
15
16
Returns:
17
int: Number of interfaces
18
"""
19
20
def getConfigurationValue(self):
21
"""
22
Get configuration value used in SetConfiguration requests.
23
24
Returns:
25
int: Configuration value (1-based)
26
"""
27
28
def getDescriptor(self):
29
"""
30
Get string descriptor index for configuration description.
31
32
Returns:
33
int: String descriptor index (0 if none)
34
"""
35
36
def getAttributes(self):
37
"""
38
Get configuration attributes bitfield.
39
40
Returns:
41
int: Attributes (bit 7: bus-powered, bit 6: self-powered, bit 5: remote wakeup)
42
"""
43
44
def getMaxPower(self):
45
"""
46
Get maximum power consumption in milliamps.
47
48
Returns:
49
int: Power consumption in mA
50
51
Note:
52
Automatically scales descriptor value based on device speed
53
(2mA units for high-speed, 8mA units for super-speed).
54
"""
55
56
def getExtra(self):
57
"""
58
Get extra (non-standard) descriptors associated with configuration.
59
60
Returns:
61
list: List of extra descriptor bytes
62
"""
63
64
def __len__(self):
65
"""
66
Get number of interfaces (same as getNumInterfaces).
67
68
Returns:
69
int: Number of interfaces
70
"""
71
72
def __iter__(self):
73
"""
74
Iterate over interfaces in this configuration.
75
76
Yields:
77
USBInterface: Interface objects
78
"""
79
80
def __getitem__(self, interface):
81
"""
82
Get interface by index.
83
84
Args:
85
interface (int): Interface index (0-based)
86
87
Returns:
88
USBInterface: Interface object
89
90
Raises:
91
IndexError: If interface index is invalid
92
TypeError: If interface is not an integer
93
"""
94
```
95
96
### Interface Descriptors
97
98
Interface descriptors group related endpoints that provide a specific function or service.
99
100
```python { .api }
101
class USBInterface:
102
def getNumSettings(self):
103
"""
104
Get number of alternate settings for this interface.
105
106
Returns:
107
int: Number of alternate settings
108
"""
109
110
def __len__(self):
111
"""
112
Get number of alternate settings (same as getNumSettings).
113
114
Returns:
115
int: Number of alternate settings
116
"""
117
118
def __iter__(self):
119
"""
120
Iterate over alternate settings in this interface.
121
122
Yields:
123
USBInterfaceSetting: Interface setting objects
124
"""
125
126
def __getitem__(self, alt_setting):
127
"""
128
Get alternate setting by index.
129
130
Args:
131
alt_setting (int): Alternate setting index (0-based)
132
133
Returns:
134
USBInterfaceSetting: Interface setting object
135
136
Raises:
137
IndexError: If alternate setting index is invalid
138
TypeError: If alt_setting is not an integer
139
"""
140
```
141
142
### Interface Setting Descriptors
143
144
Interface setting descriptors define specific configurations of an interface with its endpoints and characteristics.
145
146
```python { .api }
147
class USBInterfaceSetting:
148
def getNumber(self):
149
"""
150
Get interface number.
151
152
Returns:
153
int: Interface number (unique within configuration)
154
"""
155
156
def getAlternateSetting(self):
157
"""
158
Get alternate setting number.
159
160
Returns:
161
int: Alternate setting number (0-based)
162
"""
163
164
def getNumEndpoints(self):
165
"""
166
Get number of endpoints in this interface setting.
167
168
Returns:
169
int: Number of endpoints (excluding control endpoint 0)
170
"""
171
172
def getClass(self):
173
"""
174
Get interface class code.
175
176
Returns:
177
int: USB interface class (e.g., 3=HID, 9=Hub, 10=CDC Data)
178
"""
179
180
def getSubClass(self):
181
"""
182
Get interface subclass code.
183
184
Returns:
185
int: USB interface subclass
186
"""
187
188
def getClassTuple(self):
189
"""
190
Get (class, subclass) tuple for convenient matching.
191
192
Returns:
193
tuple[int, int]: (class, subclass) pair
194
"""
195
196
def getProtocol(self):
197
"""
198
Get interface protocol code.
199
200
Returns:
201
int: USB interface protocol
202
"""
203
204
def getDescriptor(self):
205
"""
206
Get string descriptor index for interface description.
207
208
Returns:
209
int: String descriptor index (0 if none)
210
"""
211
212
def getExtra(self):
213
"""
214
Get extra (non-standard) descriptors associated with interface.
215
216
Returns:
217
list: List of extra descriptor bytes
218
"""
219
220
def __len__(self):
221
"""
222
Get number of endpoints (same as getNumEndpoints).
223
224
Returns:
225
int: Number of endpoints
226
"""
227
228
def __iter__(self):
229
"""
230
Iterate over endpoints in this interface setting.
231
232
Yields:
233
USBEndpoint: Endpoint objects
234
"""
235
236
def __getitem__(self, endpoint):
237
"""
238
Get endpoint by index.
239
240
Args:
241
endpoint (int): Endpoint index (0-based)
242
243
Returns:
244
USBEndpoint: Endpoint object
245
246
Raises:
247
ValueError: If endpoint index is invalid
248
TypeError: If endpoint is not an integer
249
"""
250
```
251
252
### Endpoint Descriptors
253
254
Endpoint descriptors define communication endpoints with their transfer characteristics and capabilities.
255
256
```python { .api }
257
class USBEndpoint:
258
def getAddress(self):
259
"""
260
Get endpoint address including direction bit.
261
262
Returns:
263
int: Endpoint address (bit 7: direction, bits 0-3: endpoint number)
264
"""
265
266
def getAttributes(self):
267
"""
268
Get endpoint attributes defining transfer type and characteristics.
269
270
Returns:
271
int: Attributes bitfield
272
Bits 1-0: Transfer type (0=control, 1=isochronous, 2=bulk, 3=interrupt)
273
For isochronous: bits 3-2: synchronization, bits 5-4: usage
274
"""
275
276
def getMaxPacketSize(self):
277
"""
278
Get maximum packet size for this endpoint.
279
280
Returns:
281
int: Maximum packet size in bytes
282
283
Note:
284
For high-speed endpoints, this includes additional transaction
285
opportunities encoded in upper bits.
286
"""
287
288
def getInterval(self):
289
"""
290
Get polling interval for interrupt/isochronous endpoints.
291
292
Returns:
293
int: Interval value (interpretation depends on speed and transfer type)
294
"""
295
296
def getRefresh(self):
297
"""
298
Get refresh rate for audio isochronous endpoints.
299
300
Returns:
301
int: Refresh rate (audio endpoints only)
302
"""
303
304
def getSyncAddress(self):
305
"""
306
Get synchronization endpoint address for audio isochronous endpoints.
307
308
Returns:
309
int: Sync endpoint address (audio endpoints only)
310
"""
311
312
def getExtra(self):
313
"""
314
Get extra (non-standard) descriptors associated with endpoint.
315
316
Returns:
317
list: List of extra descriptor bytes
318
"""
319
```
320
321
### Device Descriptor Navigation
322
323
Access configuration descriptors from device objects and navigate the descriptor hierarchy.
324
325
```python { .api }
326
class USBDevice:
327
def __len__(self):
328
"""
329
Get number of configurations.
330
331
Returns:
332
int: Number of configurations
333
"""
334
335
def __getitem__(self, index):
336
"""
337
Get configuration by index.
338
339
Args:
340
index (int): Configuration index (0-based)
341
342
Returns:
343
USBConfiguration: Configuration object
344
"""
345
346
def iterConfigurations(self):
347
"""
348
Iterate over device configurations.
349
350
Yields:
351
USBConfiguration: Configuration objects
352
"""
353
354
def iterSettings(self):
355
"""
356
Iterate over all interface settings in all configurations.
357
358
Yields:
359
USBInterfaceSetting: Interface setting objects
360
"""
361
```
362
363
## Usage Examples
364
365
### Complete Descriptor Analysis
366
367
```python
368
import usb1
369
370
def analyze_device_descriptors(device):
371
"""Perform complete analysis of device descriptor hierarchy."""
372
print(f"\nDevice {device.getVendorID():04x}:{device.getProductID():04x}")
373
print(f" {device.getNumConfigurations()} configuration(s)")
374
375
for config_idx, config in enumerate(device.iterConfigurations()):
376
print(f"\n Configuration {config_idx + 1}:")
377
print(f" Value: {config.getConfigurationValue()}")
378
print(f" Interfaces: {config.getNumInterfaces()}")
379
print(f" Attributes: 0x{config.getAttributes():02x}")
380
print(f" Max Power: {config.getMaxPower()} mA")
381
382
# Check for configuration description
383
desc_idx = config.getDescriptor()
384
if desc_idx:
385
try:
386
desc = device.getASCIIStringDescriptor(desc_idx)
387
if desc:
388
print(f" Description: {desc}")
389
except usb1.USBError:
390
pass
391
392
for interface in config:
393
print(f"\n Interface {interface.getNumber()}:")
394
print(f" Alternate settings: {interface.getNumSettings()}")
395
396
for setting in interface:
397
print(f"\n Setting {setting.getAlternateSetting()}:")
398
print(f" Class: {setting.getClass()}")
399
print(f" Subclass: {setting.getSubClass()}")
400
print(f" Protocol: {setting.getProtocol()}")
401
print(f" Endpoints: {setting.getNumEndpoints()}")
402
403
# Check for interface description
404
desc_idx = setting.getDescriptor()
405
if desc_idx:
406
try:
407
desc = device.getASCIIStringDescriptor(desc_idx)
408
if desc:
409
print(f" Description: {desc}")
410
except usb1.USBError:
411
pass
412
413
for endpoint in setting:
414
addr = endpoint.getAddress()
415
direction = "IN" if addr & 0x80 else "OUT"
416
ep_num = addr & 0x0f
417
attrs = endpoint.getAttributes()
418
transfer_type = ["Control", "Isochronous", "Bulk", "Interrupt"][attrs & 3]
419
420
print(f" Endpoint {ep_num} {direction}:")
421
print(f" Type: {transfer_type}")
422
print(f" Max Packet: {endpoint.getMaxPacketSize()}")
423
print(f" Interval: {endpoint.getInterval()}")
424
425
with usb1.USBContext() as context:
426
for device in context.getDeviceIterator(skip_on_error=True):
427
analyze_device_descriptors(device)
428
```
429
430
### Interface Class Detection
431
432
```python
433
import usb1
434
435
# Common USB interface classes
436
USB_CLASSES = {
437
0: "Per-interface",
438
1: "Audio",
439
2: "Communications",
440
3: "HID",
441
5: "Physical",
442
6: "Image",
443
7: "Printer",
444
8: "Mass Storage",
445
9: "Hub",
446
10: "CDC Data",
447
11: "Smart Card",
448
13: "Content Security",
449
14: "Video",
450
15: "Personal Healthcare",
451
16: "Audio/Video",
452
17: "Billboard",
453
18: "USB Type-C Bridge",
454
220: "Diagnostic",
455
224: "Wireless Controller",
456
239: "Miscellaneous",
457
254: "Application Specific",
458
255: "Vendor Specific"
459
}
460
461
def find_devices_by_class(context, target_class):
462
"""Find all devices with interfaces of specified class."""
463
matching_devices = []
464
465
for device in context.getDeviceIterator(skip_on_error=True):
466
# Check device class first
467
if device.getDeviceClass() == target_class:
468
matching_devices.append((device, "device"))
469
continue
470
471
# Check interface classes
472
for config in device.iterConfigurations():
473
for interface in config:
474
for setting in interface:
475
if setting.getClass() == target_class:
476
matching_devices.append((device, f"interface {setting.getNumber()}"))
477
break
478
else:
479
continue
480
break
481
else:
482
continue
483
break
484
485
return matching_devices
486
487
def interface_class_example():
488
"""Find and display devices by interface class."""
489
with usb1.USBContext() as context:
490
# Find HID devices
491
hid_devices = find_devices_by_class(context, 3) # HID class
492
print(f"Found {len(hid_devices)} HID devices:")
493
494
for device, location in hid_devices:
495
print(f" {device.getVendorID():04x}:{device.getProductID():04x} ({location})")
496
try:
497
manufacturer = device.getManufacturer()
498
product = device.getProduct()
499
if manufacturer or product:
500
print(f" {manufacturer or 'Unknown'} - {product or 'Unknown'}")
501
except usb1.USBError:
502
pass
503
504
# Find mass storage devices
505
storage_devices = find_devices_by_class(context, 8) # Mass Storage
506
print(f"\nFound {len(storage_devices)} Mass Storage devices:")
507
508
for device, location in storage_devices:
509
print(f" {device.getVendorID():04x}:{device.getProductID():04x} ({location})")
510
511
interface_class_example()
512
```
513
514
### Endpoint Analysis and Selection
515
516
```python
517
import usb1
518
519
def analyze_endpoints(device):
520
"""Analyze endpoints and suggest optimal transfer methods."""
521
print(f"\nEndpoint analysis for {device.getVendorID():04x}:{device.getProductID():04x}")
522
523
for config in device.iterConfigurations():
524
print(f"\nConfiguration {config.getConfigurationValue()}:")
525
526
for interface in config:
527
for setting in interface:
528
if setting.getNumEndpoints() == 0:
529
continue
530
531
print(f"\n Interface {setting.getNumber()}, Setting {setting.getAlternateSetting()}:")
532
class_name = USB_CLASSES.get(setting.getClass(), f"Class {setting.getClass()}")
533
print(f" Class: {class_name}")
534
535
bulk_in = []
536
bulk_out = []
537
int_in = []
538
int_out = []
539
iso_in = []
540
iso_out = []
541
542
for endpoint in setting:
543
addr = endpoint.getAddress()
544
attrs = endpoint.getAttributes()
545
transfer_type = attrs & 3
546
is_in = bool(addr & 0x80)
547
ep_num = addr & 0x0f
548
max_packet = endpoint.getMaxPacketSize()
549
interval = endpoint.getInterval()
550
551
ep_info = {
552
'address': addr,
553
'number': ep_num,
554
'max_packet': max_packet,
555
'interval': interval
556
}
557
558
if transfer_type == 2: # Bulk
559
if is_in:
560
bulk_in.append(ep_info)
561
else:
562
bulk_out.append(ep_info)
563
elif transfer_type == 3: # Interrupt
564
if is_in:
565
int_in.append(ep_info)
566
else:
567
int_out.append(ep_info)
568
elif transfer_type == 1: # Isochronous
569
if is_in:
570
iso_in.append(ep_info)
571
else:
572
iso_out.append(ep_info)
573
574
# Print endpoint summary and usage suggestions
575
if bulk_in or bulk_out:
576
print(f" Bulk endpoints:")
577
for ep in bulk_in:
578
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes")
579
print(f" -> Use bulkRead(0x{ep['address']:02x}, length)")
580
for ep in bulk_out:
581
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes")
582
print(f" -> Use bulkWrite(0x{ep['address']:02x}, data)")
583
584
if int_in or int_out:
585
print(f" Interrupt endpoints:")
586
for ep in int_in:
587
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
588
print(f" -> Use interruptRead(0x{ep['address']:02x}, {ep['max_packet']})")
589
for ep in int_out:
590
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
591
print(f" -> Use interruptWrite(0x{ep['address']:02x}, data)")
592
593
if iso_in or iso_out:
594
print(f" Isochronous endpoints:")
595
for ep in iso_in:
596
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
597
print(f" -> Use async transfer with setIsochronous()")
598
for ep in iso_out:
599
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
600
print(f" -> Use async transfer with setIsochronous()")
601
602
with usb1.USBContext() as context:
603
for device in context.getDeviceIterator(skip_on_error=True):
604
if device.getNumConfigurations() > 0:
605
analyze_endpoints(device)
606
```
607
608
### Configuration Selection Helper
609
610
```python
611
import usb1
612
613
def select_best_configuration(device, required_class=None, min_endpoints=0):
614
"""
615
Select the best configuration based on criteria.
616
617
Args:
618
device: USBDevice to analyze
619
required_class: Required interface class (None for any)
620
min_endpoints: Minimum number of endpoints needed
621
622
Returns:
623
tuple: (config_value, interface_number, alt_setting) or None
624
"""
625
best_config = None
626
best_score = -1
627
628
for config in device.iterConfigurations():
629
config_score = 0
630
631
for interface in config:
632
for setting in interface:
633
setting_score = 0
634
635
# Check class requirement
636
if required_class is not None:
637
if setting.getClass() == required_class:
638
setting_score += 100
639
else:
640
continue # Skip if class doesn't match
641
642
# Check endpoint requirement
643
num_endpoints = setting.getNumEndpoints()
644
if num_endpoints >= min_endpoints:
645
setting_score += num_endpoints * 10
646
else:
647
continue # Skip if not enough endpoints
648
649
# Prefer interface 0, alternate setting 0
650
if setting.getNumber() == 0:
651
setting_score += 5
652
if setting.getAlternateSetting() == 0:
653
setting_score += 3
654
655
# Prefer configurations with both IN and OUT endpoints
656
has_in = False
657
has_out = False
658
for endpoint in setting:
659
if endpoint.getAddress() & 0x80:
660
has_in = True
661
else:
662
has_out = True
663
664
if has_in and has_out:
665
setting_score += 20
666
elif has_in or has_out:
667
setting_score += 10
668
669
config_score = max(config_score, setting_score)
670
671
if setting_score > best_score:
672
best_score = setting_score
673
best_config = (
674
config.getConfigurationValue(),
675
setting.getNumber(),
676
setting.getAlternateSetting()
677
)
678
679
return best_config
680
681
def configuration_selection_example():
682
"""Demonstrate automatic configuration selection."""
683
with usb1.USBContext() as context:
684
for device in context.getDeviceIterator(skip_on_error=True):
685
# Skip hubs and other system devices
686
if device.getDeviceClass() == 9: # Hub
687
continue
688
689
print(f"\nDevice {device.getVendorID():04x}:{device.getProductID():04x}")
690
691
# Try to find best configuration for different use cases
692
scenarios = [
693
("HID device", 3, 1), # HID class, at least 1 endpoint
694
("Mass storage", 8, 2), # Mass storage, at least 2 endpoints
695
("General communication", None, 2), # Any class, at least 2 endpoints
696
("Any interface", None, 0), # Any configuration
697
]
698
699
for scenario_name, req_class, min_eps in scenarios:
700
result = select_best_configuration(device, req_class, min_eps)
701
if result:
702
config_val, interface_num, alt_setting = result
703
print(f" {scenario_name}: Config {config_val}, Interface {interface_num}, Alt {alt_setting}")
704
break
705
else:
706
print(f" No suitable configuration found")
707
708
configuration_selection_example()
709
```
710
711
### Descriptor Caching and Comparison
712
713
```python
714
import usb1
715
import json
716
717
def cache_device_descriptors(device):
718
"""Cache device descriptor information as JSON-serializable data."""
719
cache = {
720
'vendor_id': device.getVendorID(),
721
'product_id': device.getProductID(),
722
'device_class': device.getDeviceClass(),
723
'device_subclass': device.getDeviceSubClass(),
724
'device_protocol': device.getDeviceProtocol(),
725
'usb_version': device.getbcdUSB(),
726
'device_version': device.getbcdDevice(),
727
'speed': device.getDeviceSpeed(),
728
'configurations': []
729
}
730
731
# Cache string descriptors
732
try:
733
cache['manufacturer'] = device.getManufacturer()
734
cache['product'] = device.getProduct()
735
cache['serial'] = device.getSerialNumber()
736
except usb1.USBError:
737
pass
738
739
# Cache configuration information
740
for config in device.iterConfigurations():
741
config_data = {
742
'value': config.getConfigurationValue(),
743
'attributes': config.getAttributes(),
744
'max_power': config.getMaxPower(),
745
'interfaces': []
746
}
747
748
for interface in config:
749
interface_data = {
750
'number': interface[0].getNumber(), # All settings have same number
751
'settings': []
752
}
753
754
for setting in interface:
755
setting_data = {
756
'alt_setting': setting.getAlternateSetting(),
757
'class': setting.getClass(),
758
'subclass': setting.getSubClass(),
759
'protocol': setting.getProtocol(),
760
'endpoints': []
761
}
762
763
for endpoint in setting:
764
endpoint_data = {
765
'address': endpoint.getAddress(),
766
'attributes': endpoint.getAttributes(),
767
'max_packet_size': endpoint.getMaxPacketSize(),
768
'interval': endpoint.getInterval()
769
}
770
setting_data['endpoints'].append(endpoint_data)
771
772
interface_data['settings'].append(setting_data)
773
774
config_data['interfaces'].append(interface_data)
775
776
cache['configurations'].append(config_data)
777
778
return cache
779
780
def compare_descriptors(cache1, cache2):
781
"""Compare two descriptor caches and report differences."""
782
differences = []
783
784
# Compare basic device info
785
basic_fields = ['vendor_id', 'product_id', 'device_class', 'usb_version']
786
for field in basic_fields:
787
if cache1.get(field) != cache2.get(field):
788
differences.append(f"{field}: {cache1.get(field)} -> {cache2.get(field)}")
789
790
# Compare configuration count
791
if len(cache1['configurations']) != len(cache2['configurations']):
792
differences.append(f"Configuration count: {len(cache1['configurations'])} -> {len(cache2['configurations'])}")
793
794
# Compare configurations
795
for i, (config1, config2) in enumerate(zip(cache1['configurations'], cache2['configurations'])):
796
if config1['max_power'] != config2['max_power']:
797
differences.append(f"Config {i+1} max power: {config1['max_power']} -> {config2['max_power']}")
798
799
if len(config1['interfaces']) != len(config2['interfaces']):
800
differences.append(f"Config {i+1} interface count: {len(config1['interfaces'])} -> {len(config2['interfaces'])}")
801
802
return differences
803
804
def descriptor_caching_example():
805
"""Demonstrate descriptor caching and comparison."""
806
device_cache = {}
807
808
with usb1.USBContext() as context:
809
print("Caching device descriptors...")
810
811
for device in context.getDeviceIterator(skip_on_error=True):
812
device_id = f"{device.getVendorID():04x}:{device.getProductID():04x}"
813
cache = cache_device_descriptors(device)
814
device_cache[device_id] = cache
815
816
print(f"Cached {device_id}: {len(cache['configurations'])} configurations")
817
818
# Save cache to file
819
with open('usb_device_cache.json', 'w') as f:
820
json.dump(device_cache, f, indent=2)
821
print(f"Saved cache for {len(device_cache)} devices")
822
823
# Example: Load and compare (in real use, this would be from a previous run)
824
print("\nComparison example (comparing cache with itself):")
825
for device_id, cache in list(device_cache.items())[:3]: # Just first 3 devices
826
differences = compare_descriptors(cache, cache)
827
if differences:
828
print(f"{device_id}: {len(differences)} differences found")
829
else:
830
print(f"{device_id}: No differences (as expected)")
831
832
descriptor_caching_example()
833
```