or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

constants-errors.mddata-transfers.mddescriptors.mddevice-communication.mddevice-management.mdevent-handling.mdindex.mdinterfaces-endpoints.mdwebusb-api.md

data-transfers.mddocs/

0

# Data Transfers

1

2

Data transfers provide methods to perform bulk, interrupt, and control data transfers with USB endpoints. This includes single transfers, continuous polling, and stream management for efficient data communication.

3

4

## Capabilities

5

6

### IN Endpoint Transfers

7

8

Receive data from USB devices using IN endpoints.

9

10

```typescript { .api }

11

/**

12

* IN Endpoint for receiving data from device

13

*/

14

interface InEndpoint extends Endpoint {

15

direction: 'in';

16

pollActive: boolean;

17

18

/**

19

* Perform a transfer to read data from the endpoint

20

* If length is greater than maxPacketSize, libusb will automatically split

21

* the transfer in multiple packets

22

* @param length - Number of bytes to read

23

* @param callback - Completion callback with error and data

24

* @returns InEndpoint instance for chaining

25

*/

26

transfer(length: number, callback: (error?: LibUSBException, data?: Buffer) => void): InEndpoint;

27

28

/**

29

* Async version of transfer

30

* @param length - Number of bytes to read

31

* @returns Promise resolving to Buffer or undefined

32

*/

33

transferAsync(length: number): Promise<Buffer | undefined>;

34

}

35

```

36

37

**Usage Examples:**

38

39

```typescript

40

import { findByIds } from 'usb';

41

42

const device = findByIds(0x1234, 0x5678);

43

if (device) {

44

device.open();

45

const interface0 = device.interface(0);

46

interface0.claim();

47

48

// Find an IN endpoint

49

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in');

50

if (inEndpoint) {

51

console.log(`Using IN endpoint: 0x${inEndpoint.address.toString(16)}`);

52

53

// Single transfer - callback style

54

inEndpoint.transfer(64, (error, data) => {

55

if (error) {

56

console.error('Transfer failed:', error.message);

57

return;

58

}

59

60

if (data) {

61

console.log(`Received ${data.length} bytes:`, data.toString('hex'));

62

}

63

64

interface0.release(() => device.close());

65

});

66

}

67

}

68

69

// Using async/await

70

async function readDataAsync() {

71

const device = findByIds(0x1234, 0x5678);

72

if (!device) return;

73

74

device.open();

75

76

try {

77

const interface0 = device.interface(0);

78

interface0.claim();

79

80

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

81

if (inEndpoint) {

82

// Read 128 bytes

83

const data = await inEndpoint.transferAsync(128);

84

if (data) {

85

console.log(`Received ${data.length} bytes`);

86

console.log('Data:', data.toString('hex'));

87

}

88

}

89

90

await interface0.releaseAsync();

91

} catch (error) {

92

console.error('Read error:', error);

93

} finally {

94

device.close();

95

}

96

}

97

98

// Reading specific data lengths

99

const deviceWithMultipleReads = findByIds(0x5678, 0x1234);

100

if (deviceWithMultipleReads) {

101

deviceWithMultipleReads.open();

102

const interface0 = deviceWithMultipleReads.interface(0);

103

interface0.claim();

104

105

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

106

if (inEndpoint) {

107

// Set custom timeout

108

inEndpoint.timeout = 2000; // 2 seconds

109

110

// Read different amounts of data

111

const readSizes = [8, 16, 32, 64, 128];

112

let readIndex = 0;

113

114

function readNext() {

115

if (readIndex >= readSizes.length) {

116

interface0.release(() => deviceWithMultipleReads.close());

117

return;

118

}

119

120

const size = readSizes[readIndex++];

121

inEndpoint.transfer(size, (error, data) => {

122

if (error) {

123

console.error(`Failed to read ${size} bytes:`, error.message);

124

} else if (data) {

125

console.log(`Read ${size} bytes: ${data.length} received`);

126

}

127

128

// Read next size

129

setTimeout(readNext, 100);

130

});

131

}

132

133

readNext();

134

}

135

}

136

```

137

138

### OUT Endpoint Transfers

139

140

Send data to USB devices using OUT endpoints.

141

142

```typescript { .api }

143

/**

144

* OUT Endpoint for sending data to device

145

*/

146

interface OutEndpoint extends Endpoint {

147

direction: 'out';

148

149

/**

150

* Perform a transfer to write data to the endpoint

151

* If length is greater than maxPacketSize, libusb will automatically split

152

* the transfer in multiple packets

153

* @param buffer - Data to write

154

* @param callback - Completion callback with error and bytes written

155

* @returns OutEndpoint instance for chaining

156

*/

157

transfer(buffer: Buffer, callback?: (error?: LibUSBException, actual: number) => void): OutEndpoint;

158

159

/**

160

* Async version of transfer

161

* @param buffer - Data to write

162

* @returns Promise resolving to number of bytes written

163

*/

164

transferAsync(buffer: Buffer): Promise<number>;

165

166

/**

167

* Transfer with Zero Length Packet (ZLP)

168

* If buffer length is multiple of max packet size, sends additional ZLP

169

* @param buffer - Data to write

170

* @param callback - Completion callback

171

*/

172

transferWithZLP(buffer: Buffer, callback: (error?: LibUSBException) => void): void;

173

}

174

```

175

176

**Usage Examples:**

177

178

```typescript

179

import { findByIds } from 'usb';

180

181

const device = findByIds(0x1234, 0x5678);

182

if (device) {

183

device.open();

184

const interface0 = device.interface(0);

185

interface0.claim();

186

187

// Find an OUT endpoint

188

const outEndpoint = interface0.endpoints.find(ep => ep.direction === 'out') as OutEndpoint;

189

if (outEndpoint) {

190

console.log(`Using OUT endpoint: 0x${outEndpoint.address.toString(16)}`);

191

192

// Send data - callback style

193

const data = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05]);

194

outEndpoint.transfer(data, (error, bytesWritten) => {

195

if (error) {

196

console.error('Transfer failed:', error.message);

197

return;

198

}

199

200

console.log(`Sent ${bytesWritten} bytes successfully`);

201

interface0.release(() => device.close());

202

});

203

}

204

}

205

206

// Using async/await

207

async function sendDataAsync() {

208

const device = findByIds(0x1234, 0x5678);

209

if (!device) return;

210

211

device.open();

212

213

try {

214

const interface0 = device.interface(0);

215

interface0.claim();

216

217

const outEndpoint = interface0.endpoints.find(ep => ep.direction === 'out') as OutEndpoint;

218

if (outEndpoint) {

219

// Send text data

220

const textData = Buffer.from('Hello USB Device!', 'utf8');

221

const bytesWritten = await outEndpoint.transferAsync(textData);

222

console.log(`Sent ${bytesWritten} bytes of text`);

223

224

// Send binary data

225

const binaryData = Buffer.from([0xFF, 0x00, 0xAA, 0x55, 0x12, 0x34]);

226

const bytesSent = await outEndpoint.transferAsync(binaryData);

227

console.log(`Sent ${bytesSent} bytes of binary data`);

228

}

229

230

await interface0.releaseAsync();

231

} catch (error) {

232

console.error('Send error:', error);

233

} finally {

234

device.close();

235

}

236

}

237

238

// Using Zero Length Packet transfers

239

const deviceWithZLP = findByIds(0x5678, 0x1234);

240

if (deviceWithZLP) {

241

deviceWithZLP.open();

242

const interface0 = deviceWithZLP.interface(0);

243

interface0.claim();

244

245

const outEndpoint = interface0.endpoints.find(ep => ep.direction === 'out') as OutEndpoint;

246

if (outEndpoint) {

247

const maxPacketSize = outEndpoint.descriptor.wMaxPacketSize;

248

console.log(`Max packet size: ${maxPacketSize}`);

249

250

// Create data that's exactly multiple of max packet size

251

const dataSize = maxPacketSize * 2; // 2 packets worth

252

const data = Buffer.alloc(dataSize, 0xAA);

253

254

// This will send the data + a zero length packet to signal end

255

outEndpoint.transferWithZLP(data, (error) => {

256

if (error) {

257

console.error('ZLP transfer failed:', error.message);

258

} else {

259

console.log(`Sent ${dataSize} bytes with ZLP`);

260

}

261

262

interface0.release(() => deviceWithZLP.close());

263

});

264

}

265

}

266

267

// Chunked data sending

268

async function sendLargeData(outEndpoint: OutEndpoint, data: Buffer, chunkSize: number = 64) {

269

const totalSize = data.length;

270

let offset = 0;

271

let totalSent = 0;

272

273

console.log(`Sending ${totalSize} bytes in chunks of ${chunkSize}`);

274

275

while (offset < totalSize) {

276

const remainingBytes = totalSize - offset;

277

const currentChunkSize = Math.min(chunkSize, remainingBytes);

278

const chunk = data.slice(offset, offset + currentChunkSize);

279

280

try {

281

const bytesSent = await outEndpoint.transferAsync(chunk);

282

totalSent += bytesSent;

283

offset += currentChunkSize;

284

285

console.log(`Sent chunk: ${bytesSent} bytes (total: ${totalSent}/${totalSize})`);

286

287

// Small delay between chunks if needed

288

await new Promise(resolve => setTimeout(resolve, 10));

289

290

} catch (error) {

291

console.error(`Failed to send chunk at offset ${offset}:`, error);

292

break;

293

}

294

}

295

296

return totalSent;

297

}

298

```

299

300

### Continuous Polling

301

302

Set up continuous data polling for streaming data from IN endpoints.

303

304

```typescript { .api }

305

/**

306

* Start polling the endpoint for continuous data flow

307

* The library will keep nTransfers transfers of size transferSize pending

308

* in the kernel at all times to ensure continuous data flow

309

* @param nTransfers - Number of concurrent transfers (default: 3)

310

* @param transferSize - Size of each transfer (default: endpoint maxPacketSize)

311

* @param callback - Optional transfer completion callback

312

* @returns Array of Transfer objects

313

*/

314

startPoll(nTransfers?: number, transferSize?: number, callback?: (error?: LibUSBException, buffer: Buffer, actualLength: number, cancelled: boolean) => void): Transfer[];

315

316

/**

317

* Stop polling

318

* Further data may still be received. The 'end' event is emitted when all transfers complete

319

* @param callback - Optional completion callback

320

*/

321

stopPoll(callback?: () => void): void;

322

```

323

324

**Usage Examples:**

325

326

```typescript

327

import { findByIds } from 'usb';

328

329

const device = findByIds(0x1234, 0x5678);

330

if (device) {

331

device.open();

332

const interface0 = device.interface(0);

333

interface0.claim();

334

335

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

336

if (inEndpoint) {

337

console.log(`Starting polling on endpoint 0x${inEndpoint.address.toString(16)}`);

338

339

// Set up event handlers

340

inEndpoint.on('data', (data: Buffer) => {

341

console.log(`Received ${data.length} bytes:`, data.toString('hex'));

342

});

343

344

inEndpoint.on('error', (error) => {

345

console.error('Polling error:', error.message);

346

inEndpoint.stopPoll();

347

});

348

349

inEndpoint.on('end', () => {

350

console.log('Polling ended');

351

interface0.release(() => device.close());

352

});

353

354

// Start polling with 3 concurrent transfers of 64 bytes each

355

const transfers = inEndpoint.startPoll(3, 64);

356

console.log(`Started polling with ${transfers.length} concurrent transfers`);

357

358

// Stop polling after 10 seconds

359

setTimeout(() => {

360

console.log('Stopping polling...');

361

inEndpoint.stopPoll();

362

}, 10000);

363

}

364

}

365

366

// Advanced polling with custom parameters

367

const deviceAdvanced = findByIds(0x5678, 0x1234);

368

if (deviceAdvanced) {

369

deviceAdvanced.open();

370

const interface0 = deviceAdvanced.interface(0);

371

interface0.claim();

372

373

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

374

if (inEndpoint) {

375

let totalBytesReceived = 0;

376

let packetsReceived = 0;

377

378

// Set up data handler

379

inEndpoint.on('data', (data: Buffer) => {

380

totalBytesReceived += data.length;

381

packetsReceived++;

382

383

if (packetsReceived % 100 === 0) {

384

console.log(`Received ${packetsReceived} packets, ${totalBytesReceived} total bytes`);

385

}

386

});

387

388

// Set up error handler

389

inEndpoint.on('error', (error) => {

390

console.error('Polling error:', error);

391

inEndpoint.stopPoll(() => {

392

console.log(`Final stats: ${packetsReceived} packets, ${totalBytesReceived} bytes`);

393

});

394

});

395

396

// Start high-throughput polling

397

const maxPacketSize = inEndpoint.descriptor.wMaxPacketSize;

398

const nTransfers = 8; // More concurrent transfers for higher throughput

399

const transferSize = maxPacketSize * 4; // Larger transfer size

400

401

console.log(`Starting high-throughput polling:`);

402

console.log(` Transfers: ${nTransfers}`);

403

console.log(` Transfer size: ${transferSize} bytes`);

404

console.log(` Max packet size: ${maxPacketSize}`);

405

406

inEndpoint.startPoll(nTransfers, transferSize, (error, buffer, actualLength, cancelled) => {

407

if (error && !cancelled) {

408

console.error('Transfer callback error:', error.message);

409

}

410

});

411

412

// Monitor polling status

413

const statusInterval = setInterval(() => {

414

console.log(`Polling active: ${inEndpoint.pollActive}`);

415

if (!inEndpoint.pollActive) {

416

clearInterval(statusInterval);

417

}

418

}, 5000);

419

420

// Stop after 30 seconds

421

setTimeout(() => {

422

inEndpoint.stopPoll(() => {

423

console.log('Polling stopped gracefully');

424

clearInterval(statusInterval);

425

interface0.release(() => deviceAdvanced.close());

426

});

427

}, 30000);

428

}

429

}

430

```

431

432

### Transfer Management

433

434

Create and manage individual transfers for fine-grained control.

435

436

```typescript { .api }

437

/**

438

* Create a new Transfer object for this endpoint

439

* @param timeout - Timeout for the transfer (0 means unlimited)

440

* @param callback - Transfer completion callback

441

* @returns Transfer object

442

*/

443

makeTransfer(timeout: number, callback: (error?: LibUSBException, buffer: Buffer, actualLength: number) => void): Transfer;

444

445

/**

446

* Transfer class for USB transfers

447

*/

448

class Transfer {

449

/**

450

* Create a new Transfer object

451

* @param device - USB Device object

452

* @param endpointAddr - Endpoint address

453

* @param type - Transfer type (BULK, INTERRUPT, etc.)

454

* @param timeout - Transfer timeout in milliseconds

455

* @param callback - Transfer completion callback

456

*/

457

constructor(

458

device: Device,

459

endpointAddr: number,

460

type: number,

461

timeout: number,

462

callback: (error?: LibUSBException, buffer: Buffer, actualLength: number) => void

463

);

464

465

/**

466

* Submit the transfer

467

* @param buffer - Buffer for data (IN: where data will be written, OUT: data to send)

468

* @param callback - Optional completion callback

469

* @returns Transfer instance

470

*/

471

submit(buffer: Buffer, callback?: (error?: LibUSBException, buffer: Buffer, actualLength: number) => void): Transfer;

472

473

/**

474

* Cancel the transfer

475

* @returns true if transfer was canceled, false if it wasn't in pending state

476

*/

477

cancel(): boolean;

478

}

479

```

480

481

**Usage Examples:**

482

483

```typescript

484

import { findByIds } from 'usb';

485

486

const device = findByIds(0x1234, 0x5678);

487

if (device) {

488

device.open();

489

const interface0 = device.interface(0);

490

interface0.claim();

491

492

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

493

if (inEndpoint) {

494

// Create a custom transfer with 5 second timeout

495

const transfer = inEndpoint.makeTransfer(5000, (error, buffer, actualLength) => {

496

if (error) {

497

console.error('Transfer error:', error.message);

498

} else {

499

console.log(`Transfer completed: ${actualLength} bytes received`);

500

console.log('Data:', buffer.slice(0, actualLength).toString('hex'));

501

}

502

503

interface0.release(() => device.close());

504

});

505

506

// Submit the transfer with a 128-byte buffer

507

const buffer = Buffer.alloc(128);

508

transfer.submit(buffer);

509

510

console.log('Transfer submitted, waiting for completion...');

511

512

// Cancel the transfer after 2 seconds if needed

513

setTimeout(() => {

514

const cancelled = transfer.cancel();

515

if (cancelled) {

516

console.log('Transfer was cancelled');

517

} else {

518

console.log('Transfer was not in pending state (already completed or not submitted)');

519

}

520

}, 2000);

521

}

522

}

523

524

// Multiple concurrent transfers

525

const deviceConcurrent = findByIds(0x5678, 0x1234);

526

if (deviceConcurrent) {

527

deviceConcurrent.open();

528

const interface0 = deviceConcurrent.interface(0);

529

interface0.claim();

530

531

const inEndpoint = interface0.endpoints.find(ep => ep.direction === 'in') as InEndpoint;

532

if (inEndpoint) {

533

const transfers: Transfer[] = [];

534

let completedTransfers = 0;

535

const totalTransfers = 5;

536

537

// Create multiple transfers

538

for (let i = 0; i < totalTransfers; i++) {

539

const transfer = inEndpoint.makeTransfer(3000, (error, buffer, actualLength) => {

540

completedTransfers++;

541

542

if (error) {

543

console.error(`Transfer ${i} error:`, error.message);

544

} else {

545

console.log(`Transfer ${i} completed: ${actualLength} bytes`);

546

}

547

548

// Clean up when all transfers complete

549

if (completedTransfers === totalTransfers) {

550

console.log('All transfers completed');

551

interface0.release(() => deviceConcurrent.close());

552

}

553

});

554

555

transfers.push(transfer);

556

557

// Submit each transfer

558

const buffer = Buffer.alloc(64);

559

transfer.submit(buffer);

560

}

561

562

console.log(`Submitted ${totalTransfers} concurrent transfers`);

563

564

// Cancel all transfers after 10 seconds if needed

565

setTimeout(() => {

566

console.log('Cancelling all pending transfers...');

567

transfers.forEach((transfer, index) => {

568

const cancelled = transfer.cancel();

569

if (cancelled) {

570

console.log(`Transfer ${index} cancelled`);

571

}

572

});

573

}, 10000);

574

}

575

}

576

577

// Transfer with retry logic

578

async function transferWithRetry(endpoint: InEndpoint, length: number, maxRetries: number = 3): Promise<Buffer | null> {

579

for (let attempt = 1; attempt <= maxRetries; attempt++) {

580

try {

581

console.log(`Transfer attempt ${attempt}/${maxRetries}`);

582

583

const data = await endpoint.transferAsync(length);

584

if (data) {

585

console.log(`Transfer succeeded on attempt ${attempt}`);

586

return data;

587

}

588

} catch (error) {

589

console.error(`Transfer attempt ${attempt} failed:`, error);

590

591

if (attempt === maxRetries) {

592

console.error('All transfer attempts failed');

593

return null;

594

}

595

596

// Wait before retry

597

await new Promise(resolve => setTimeout(resolve, 1000 * attempt));

598

}

599

}

600

601

return null;

602

}

603

```