or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

async-transfers.mddescriptors.mddevice-access.mdindex.mdsync-transfers.mdusb-context.md

descriptors.mddocs/

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

```