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
```