Pure-python wrapper for libusb-1.0 providing comprehensive USB device access with support for all transfer types
—
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.
Configuration descriptors represent complete device configurations containing all interfaces and their settings.
class USBConfiguration:
def getNumInterfaces(self):
"""
Get number of interfaces in this configuration.
Returns:
int: Number of interfaces
"""
def getConfigurationValue(self):
"""
Get configuration value used in SetConfiguration requests.
Returns:
int: Configuration value (1-based)
"""
def getDescriptor(self):
"""
Get string descriptor index for configuration description.
Returns:
int: String descriptor index (0 if none)
"""
def getAttributes(self):
"""
Get configuration attributes bitfield.
Returns:
int: Attributes (bit 7: bus-powered, bit 6: self-powered, bit 5: remote wakeup)
"""
def getMaxPower(self):
"""
Get maximum power consumption in milliamps.
Returns:
int: Power consumption in mA
Note:
Automatically scales descriptor value based on device speed
(2mA units for high-speed, 8mA units for super-speed).
"""
def getExtra(self):
"""
Get extra (non-standard) descriptors associated with configuration.
Returns:
list: List of extra descriptor bytes
"""
def __len__(self):
"""
Get number of interfaces (same as getNumInterfaces).
Returns:
int: Number of interfaces
"""
def __iter__(self):
"""
Iterate over interfaces in this configuration.
Yields:
USBInterface: Interface objects
"""
def __getitem__(self, interface):
"""
Get interface by index.
Args:
interface (int): Interface index (0-based)
Returns:
USBInterface: Interface object
Raises:
IndexError: If interface index is invalid
TypeError: If interface is not an integer
"""Interface descriptors group related endpoints that provide a specific function or service.
class USBInterface:
def getNumSettings(self):
"""
Get number of alternate settings for this interface.
Returns:
int: Number of alternate settings
"""
def __len__(self):
"""
Get number of alternate settings (same as getNumSettings).
Returns:
int: Number of alternate settings
"""
def __iter__(self):
"""
Iterate over alternate settings in this interface.
Yields:
USBInterfaceSetting: Interface setting objects
"""
def __getitem__(self, alt_setting):
"""
Get alternate setting by index.
Args:
alt_setting (int): Alternate setting index (0-based)
Returns:
USBInterfaceSetting: Interface setting object
Raises:
IndexError: If alternate setting index is invalid
TypeError: If alt_setting is not an integer
"""Interface setting descriptors define specific configurations of an interface with its endpoints and characteristics.
class USBInterfaceSetting:
def getNumber(self):
"""
Get interface number.
Returns:
int: Interface number (unique within configuration)
"""
def getAlternateSetting(self):
"""
Get alternate setting number.
Returns:
int: Alternate setting number (0-based)
"""
def getNumEndpoints(self):
"""
Get number of endpoints in this interface setting.
Returns:
int: Number of endpoints (excluding control endpoint 0)
"""
def getClass(self):
"""
Get interface class code.
Returns:
int: USB interface class (e.g., 3=HID, 9=Hub, 10=CDC Data)
"""
def getSubClass(self):
"""
Get interface subclass code.
Returns:
int: USB interface subclass
"""
def getClassTuple(self):
"""
Get (class, subclass) tuple for convenient matching.
Returns:
tuple[int, int]: (class, subclass) pair
"""
def getProtocol(self):
"""
Get interface protocol code.
Returns:
int: USB interface protocol
"""
def getDescriptor(self):
"""
Get string descriptor index for interface description.
Returns:
int: String descriptor index (0 if none)
"""
def getExtra(self):
"""
Get extra (non-standard) descriptors associated with interface.
Returns:
list: List of extra descriptor bytes
"""
def __len__(self):
"""
Get number of endpoints (same as getNumEndpoints).
Returns:
int: Number of endpoints
"""
def __iter__(self):
"""
Iterate over endpoints in this interface setting.
Yields:
USBEndpoint: Endpoint objects
"""
def __getitem__(self, endpoint):
"""
Get endpoint by index.
Args:
endpoint (int): Endpoint index (0-based)
Returns:
USBEndpoint: Endpoint object
Raises:
ValueError: If endpoint index is invalid
TypeError: If endpoint is not an integer
"""Endpoint descriptors define communication endpoints with their transfer characteristics and capabilities.
class USBEndpoint:
def getAddress(self):
"""
Get endpoint address including direction bit.
Returns:
int: Endpoint address (bit 7: direction, bits 0-3: endpoint number)
"""
def getAttributes(self):
"""
Get endpoint attributes defining transfer type and characteristics.
Returns:
int: Attributes bitfield
Bits 1-0: Transfer type (0=control, 1=isochronous, 2=bulk, 3=interrupt)
For isochronous: bits 3-2: synchronization, bits 5-4: usage
"""
def getMaxPacketSize(self):
"""
Get maximum packet size for this endpoint.
Returns:
int: Maximum packet size in bytes
Note:
For high-speed endpoints, this includes additional transaction
opportunities encoded in upper bits.
"""
def getInterval(self):
"""
Get polling interval for interrupt/isochronous endpoints.
Returns:
int: Interval value (interpretation depends on speed and transfer type)
"""
def getRefresh(self):
"""
Get refresh rate for audio isochronous endpoints.
Returns:
int: Refresh rate (audio endpoints only)
"""
def getSyncAddress(self):
"""
Get synchronization endpoint address for audio isochronous endpoints.
Returns:
int: Sync endpoint address (audio endpoints only)
"""
def getExtra(self):
"""
Get extra (non-standard) descriptors associated with endpoint.
Returns:
list: List of extra descriptor bytes
"""Access configuration descriptors from device objects and navigate the descriptor hierarchy.
class USBDevice:
def __len__(self):
"""
Get number of configurations.
Returns:
int: Number of configurations
"""
def __getitem__(self, index):
"""
Get configuration by index.
Args:
index (int): Configuration index (0-based)
Returns:
USBConfiguration: Configuration object
"""
def iterConfigurations(self):
"""
Iterate over device configurations.
Yields:
USBConfiguration: Configuration objects
"""
def iterSettings(self):
"""
Iterate over all interface settings in all configurations.
Yields:
USBInterfaceSetting: Interface setting objects
"""import usb1
def analyze_device_descriptors(device):
"""Perform complete analysis of device descriptor hierarchy."""
print(f"\nDevice {device.getVendorID():04x}:{device.getProductID():04x}")
print(f" {device.getNumConfigurations()} configuration(s)")
for config_idx, config in enumerate(device.iterConfigurations()):
print(f"\n Configuration {config_idx + 1}:")
print(f" Value: {config.getConfigurationValue()}")
print(f" Interfaces: {config.getNumInterfaces()}")
print(f" Attributes: 0x{config.getAttributes():02x}")
print(f" Max Power: {config.getMaxPower()} mA")
# Check for configuration description
desc_idx = config.getDescriptor()
if desc_idx:
try:
desc = device.getASCIIStringDescriptor(desc_idx)
if desc:
print(f" Description: {desc}")
except usb1.USBError:
pass
for interface in config:
print(f"\n Interface {interface.getNumber()}:")
print(f" Alternate settings: {interface.getNumSettings()}")
for setting in interface:
print(f"\n Setting {setting.getAlternateSetting()}:")
print(f" Class: {setting.getClass()}")
print(f" Subclass: {setting.getSubClass()}")
print(f" Protocol: {setting.getProtocol()}")
print(f" Endpoints: {setting.getNumEndpoints()}")
# Check for interface description
desc_idx = setting.getDescriptor()
if desc_idx:
try:
desc = device.getASCIIStringDescriptor(desc_idx)
if desc:
print(f" Description: {desc}")
except usb1.USBError:
pass
for endpoint in setting:
addr = endpoint.getAddress()
direction = "IN" if addr & 0x80 else "OUT"
ep_num = addr & 0x0f
attrs = endpoint.getAttributes()
transfer_type = ["Control", "Isochronous", "Bulk", "Interrupt"][attrs & 3]
print(f" Endpoint {ep_num} {direction}:")
print(f" Type: {transfer_type}")
print(f" Max Packet: {endpoint.getMaxPacketSize()}")
print(f" Interval: {endpoint.getInterval()}")
with usb1.USBContext() as context:
for device in context.getDeviceIterator(skip_on_error=True):
analyze_device_descriptors(device)import usb1
# Common USB interface classes
USB_CLASSES = {
0: "Per-interface",
1: "Audio",
2: "Communications",
3: "HID",
5: "Physical",
6: "Image",
7: "Printer",
8: "Mass Storage",
9: "Hub",
10: "CDC Data",
11: "Smart Card",
13: "Content Security",
14: "Video",
15: "Personal Healthcare",
16: "Audio/Video",
17: "Billboard",
18: "USB Type-C Bridge",
220: "Diagnostic",
224: "Wireless Controller",
239: "Miscellaneous",
254: "Application Specific",
255: "Vendor Specific"
}
def find_devices_by_class(context, target_class):
"""Find all devices with interfaces of specified class."""
matching_devices = []
for device in context.getDeviceIterator(skip_on_error=True):
# Check device class first
if device.getDeviceClass() == target_class:
matching_devices.append((device, "device"))
continue
# Check interface classes
for config in device.iterConfigurations():
for interface in config:
for setting in interface:
if setting.getClass() == target_class:
matching_devices.append((device, f"interface {setting.getNumber()}"))
break
else:
continue
break
else:
continue
break
return matching_devices
def interface_class_example():
"""Find and display devices by interface class."""
with usb1.USBContext() as context:
# Find HID devices
hid_devices = find_devices_by_class(context, 3) # HID class
print(f"Found {len(hid_devices)} HID devices:")
for device, location in hid_devices:
print(f" {device.getVendorID():04x}:{device.getProductID():04x} ({location})")
try:
manufacturer = device.getManufacturer()
product = device.getProduct()
if manufacturer or product:
print(f" {manufacturer or 'Unknown'} - {product or 'Unknown'}")
except usb1.USBError:
pass
# Find mass storage devices
storage_devices = find_devices_by_class(context, 8) # Mass Storage
print(f"\nFound {len(storage_devices)} Mass Storage devices:")
for device, location in storage_devices:
print(f" {device.getVendorID():04x}:{device.getProductID():04x} ({location})")
interface_class_example()import usb1
def analyze_endpoints(device):
"""Analyze endpoints and suggest optimal transfer methods."""
print(f"\nEndpoint analysis for {device.getVendorID():04x}:{device.getProductID():04x}")
for config in device.iterConfigurations():
print(f"\nConfiguration {config.getConfigurationValue()}:")
for interface in config:
for setting in interface:
if setting.getNumEndpoints() == 0:
continue
print(f"\n Interface {setting.getNumber()}, Setting {setting.getAlternateSetting()}:")
class_name = USB_CLASSES.get(setting.getClass(), f"Class {setting.getClass()}")
print(f" Class: {class_name}")
bulk_in = []
bulk_out = []
int_in = []
int_out = []
iso_in = []
iso_out = []
for endpoint in setting:
addr = endpoint.getAddress()
attrs = endpoint.getAttributes()
transfer_type = attrs & 3
is_in = bool(addr & 0x80)
ep_num = addr & 0x0f
max_packet = endpoint.getMaxPacketSize()
interval = endpoint.getInterval()
ep_info = {
'address': addr,
'number': ep_num,
'max_packet': max_packet,
'interval': interval
}
if transfer_type == 2: # Bulk
if is_in:
bulk_in.append(ep_info)
else:
bulk_out.append(ep_info)
elif transfer_type == 3: # Interrupt
if is_in:
int_in.append(ep_info)
else:
int_out.append(ep_info)
elif transfer_type == 1: # Isochronous
if is_in:
iso_in.append(ep_info)
else:
iso_out.append(ep_info)
# Print endpoint summary and usage suggestions
if bulk_in or bulk_out:
print(f" Bulk endpoints:")
for ep in bulk_in:
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes")
print(f" -> Use bulkRead(0x{ep['address']:02x}, length)")
for ep in bulk_out:
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes")
print(f" -> Use bulkWrite(0x{ep['address']:02x}, data)")
if int_in or int_out:
print(f" Interrupt endpoints:")
for ep in int_in:
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
print(f" -> Use interruptRead(0x{ep['address']:02x}, {ep['max_packet']})")
for ep in int_out:
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
print(f" -> Use interruptWrite(0x{ep['address']:02x}, data)")
if iso_in or iso_out:
print(f" Isochronous endpoints:")
for ep in iso_in:
print(f" IN 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
print(f" -> Use async transfer with setIsochronous()")
for ep in iso_out:
print(f" OUT 0x{ep['address']:02x}: {ep['max_packet']} bytes, interval {ep['interval']}")
print(f" -> Use async transfer with setIsochronous()")
with usb1.USBContext() as context:
for device in context.getDeviceIterator(skip_on_error=True):
if device.getNumConfigurations() > 0:
analyze_endpoints(device)import usb1
def select_best_configuration(device, required_class=None, min_endpoints=0):
"""
Select the best configuration based on criteria.
Args:
device: USBDevice to analyze
required_class: Required interface class (None for any)
min_endpoints: Minimum number of endpoints needed
Returns:
tuple: (config_value, interface_number, alt_setting) or None
"""
best_config = None
best_score = -1
for config in device.iterConfigurations():
config_score = 0
for interface in config:
for setting in interface:
setting_score = 0
# Check class requirement
if required_class is not None:
if setting.getClass() == required_class:
setting_score += 100
else:
continue # Skip if class doesn't match
# Check endpoint requirement
num_endpoints = setting.getNumEndpoints()
if num_endpoints >= min_endpoints:
setting_score += num_endpoints * 10
else:
continue # Skip if not enough endpoints
# Prefer interface 0, alternate setting 0
if setting.getNumber() == 0:
setting_score += 5
if setting.getAlternateSetting() == 0:
setting_score += 3
# Prefer configurations with both IN and OUT endpoints
has_in = False
has_out = False
for endpoint in setting:
if endpoint.getAddress() & 0x80:
has_in = True
else:
has_out = True
if has_in and has_out:
setting_score += 20
elif has_in or has_out:
setting_score += 10
config_score = max(config_score, setting_score)
if setting_score > best_score:
best_score = setting_score
best_config = (
config.getConfigurationValue(),
setting.getNumber(),
setting.getAlternateSetting()
)
return best_config
def configuration_selection_example():
"""Demonstrate automatic configuration selection."""
with usb1.USBContext() as context:
for device in context.getDeviceIterator(skip_on_error=True):
# Skip hubs and other system devices
if device.getDeviceClass() == 9: # Hub
continue
print(f"\nDevice {device.getVendorID():04x}:{device.getProductID():04x}")
# Try to find best configuration for different use cases
scenarios = [
("HID device", 3, 1), # HID class, at least 1 endpoint
("Mass storage", 8, 2), # Mass storage, at least 2 endpoints
("General communication", None, 2), # Any class, at least 2 endpoints
("Any interface", None, 0), # Any configuration
]
for scenario_name, req_class, min_eps in scenarios:
result = select_best_configuration(device, req_class, min_eps)
if result:
config_val, interface_num, alt_setting = result
print(f" {scenario_name}: Config {config_val}, Interface {interface_num}, Alt {alt_setting}")
break
else:
print(f" No suitable configuration found")
configuration_selection_example()import usb1
import json
def cache_device_descriptors(device):
"""Cache device descriptor information as JSON-serializable data."""
cache = {
'vendor_id': device.getVendorID(),
'product_id': device.getProductID(),
'device_class': device.getDeviceClass(),
'device_subclass': device.getDeviceSubClass(),
'device_protocol': device.getDeviceProtocol(),
'usb_version': device.getbcdUSB(),
'device_version': device.getbcdDevice(),
'speed': device.getDeviceSpeed(),
'configurations': []
}
# Cache string descriptors
try:
cache['manufacturer'] = device.getManufacturer()
cache['product'] = device.getProduct()
cache['serial'] = device.getSerialNumber()
except usb1.USBError:
pass
# Cache configuration information
for config in device.iterConfigurations():
config_data = {
'value': config.getConfigurationValue(),
'attributes': config.getAttributes(),
'max_power': config.getMaxPower(),
'interfaces': []
}
for interface in config:
interface_data = {
'number': interface[0].getNumber(), # All settings have same number
'settings': []
}
for setting in interface:
setting_data = {
'alt_setting': setting.getAlternateSetting(),
'class': setting.getClass(),
'subclass': setting.getSubClass(),
'protocol': setting.getProtocol(),
'endpoints': []
}
for endpoint in setting:
endpoint_data = {
'address': endpoint.getAddress(),
'attributes': endpoint.getAttributes(),
'max_packet_size': endpoint.getMaxPacketSize(),
'interval': endpoint.getInterval()
}
setting_data['endpoints'].append(endpoint_data)
interface_data['settings'].append(setting_data)
config_data['interfaces'].append(interface_data)
cache['configurations'].append(config_data)
return cache
def compare_descriptors(cache1, cache2):
"""Compare two descriptor caches and report differences."""
differences = []
# Compare basic device info
basic_fields = ['vendor_id', 'product_id', 'device_class', 'usb_version']
for field in basic_fields:
if cache1.get(field) != cache2.get(field):
differences.append(f"{field}: {cache1.get(field)} -> {cache2.get(field)}")
# Compare configuration count
if len(cache1['configurations']) != len(cache2['configurations']):
differences.append(f"Configuration count: {len(cache1['configurations'])} -> {len(cache2['configurations'])}")
# Compare configurations
for i, (config1, config2) in enumerate(zip(cache1['configurations'], cache2['configurations'])):
if config1['max_power'] != config2['max_power']:
differences.append(f"Config {i+1} max power: {config1['max_power']} -> {config2['max_power']}")
if len(config1['interfaces']) != len(config2['interfaces']):
differences.append(f"Config {i+1} interface count: {len(config1['interfaces'])} -> {len(config2['interfaces'])}")
return differences
def descriptor_caching_example():
"""Demonstrate descriptor caching and comparison."""
device_cache = {}
with usb1.USBContext() as context:
print("Caching device descriptors...")
for device in context.getDeviceIterator(skip_on_error=True):
device_id = f"{device.getVendorID():04x}:{device.getProductID():04x}"
cache = cache_device_descriptors(device)
device_cache[device_id] = cache
print(f"Cached {device_id}: {len(cache['configurations'])} configurations")
# Save cache to file
with open('usb_device_cache.json', 'w') as f:
json.dump(device_cache, f, indent=2)
print(f"Saved cache for {len(device_cache)} devices")
# Example: Load and compare (in real use, this would be from a previous run)
print("\nComparison example (comparing cache with itself):")
for device_id, cache in list(device_cache.items())[:3]: # Just first 3 devices
differences = compare_descriptors(cache, cache)
if differences:
print(f"{device_id}: {len(differences)} differences found")
else:
print(f"{device_id}: No differences (as expected)")
descriptor_caching_example()Install with Tessl CLI
npx tessl i tessl/pypi-libusb1