0
# Event Handling
1
2
Event handling provides methods to monitor USB device attach/detach events and handle hotplug scenarios. This includes automatic device discovery, event-driven programming, and robust handling of device connection changes.
3
4
## Capabilities
5
6
### Device Events
7
8
Monitor USB device attach and detach events.
9
10
```typescript { .api }
11
/**
12
* USB device events interface
13
*/
14
interface DeviceEvents {
15
/** Device attached event - passes Device object */
16
attach: Device;
17
18
/** Device detached event - passes Device object */
19
detach: Device;
20
21
/** Device IDs attached event - passes vendor/product ID object */
22
attachIds: { idVendor: number; idProduct: number };
23
24
/** Device IDs detached event - passes vendor/product ID object */
25
detachIds: { idVendor: number; idProduct: number };
26
27
/** New event listener added */
28
newListener: keyof DeviceEvents;
29
30
/** Event listener removed */
31
removeListener: keyof DeviceEvents;
32
}
33
34
/**
35
* USB module extends EventEmitter for device events
36
*/
37
interface USB extends EventEmitter {
38
/**
39
* Add event listener for device events
40
* @param event - Event name
41
* @param listener - Event handler function
42
*/
43
on<K extends keyof DeviceEvents>(event: K, listener: (arg: DeviceEvents[K]) => void): void;
44
45
/**
46
* Remove event listener
47
* @param event - Event name
48
* @param listener - Event handler function
49
*/
50
off<K extends keyof DeviceEvents>(event: K, listener: (arg: DeviceEvents[K]) => void): void;
51
52
/**
53
* Add one-time event listener
54
* @param event - Event name
55
* @param listener - Event handler function
56
*/
57
once<K extends keyof DeviceEvents>(event: K, listener: (arg: DeviceEvents[K]) => void): void;
58
}
59
```
60
61
**Usage Examples:**
62
63
```typescript
64
import { usb } from 'usb';
65
66
// Listen for device attach events
67
usb.on('attach', (device) => {
68
console.log('Device attached:');
69
console.log(` Vendor ID: 0x${device.deviceDescriptor.idVendor.toString(16).padStart(4, '0')}`);
70
console.log(` Product ID: 0x${device.deviceDescriptor.idProduct.toString(16).padStart(4, '0')}`);
71
console.log(` Bus: ${device.busNumber}, Address: ${device.deviceAddress}`);
72
73
// You can immediately use the device
74
try {
75
device.open();
76
console.log('Device opened successfully');
77
78
// Get device strings if available
79
if (device.deviceDescriptor.iManufacturer > 0) {
80
device.getStringDescriptor(device.deviceDescriptor.iManufacturer, (error, manufacturer) => {
81
if (!error && manufacturer) {
82
console.log(` Manufacturer: ${manufacturer}`);
83
}
84
});
85
}
86
87
device.close();
88
} catch (error) {
89
console.log('Could not open device (may be system device)');
90
}
91
});
92
93
// Listen for device detach events
94
usb.on('detach', (device) => {
95
console.log('Device detached:');
96
console.log(` Vendor ID: 0x${device.deviceDescriptor.idVendor.toString(16).padStart(4, '0')}`);
97
console.log(` Product ID: 0x${device.deviceDescriptor.idProduct.toString(16).padStart(4, '0')}`);
98
});
99
100
console.log('Monitoring USB device events. Connect or disconnect devices to see events.');
101
102
// Remove event listeners when done
103
process.on('SIGINT', () => {
104
console.log('Stopping USB event monitoring...');
105
usb.removeAllListeners('attach');
106
usb.removeAllListeners('detach');
107
process.exit(0);
108
});
109
```
110
111
### Hotplug Configuration
112
113
Configure hotplug detection behavior and polling.
114
115
```typescript { .api }
116
/**
117
* USB module hotplug properties
118
*/
119
interface USB {
120
/** Force polling loop for hotplug events */
121
pollHotplug: boolean;
122
123
/** Hotplug polling loop delay in milliseconds (default: 500) */
124
pollHotplugDelay: number;
125
}
126
127
/**
128
* Reference/unreference hotplug events from event loop
129
*/
130
function refHotplugEvents(): void;
131
function unrefHotplugEvents(): void;
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import { usb, refHotplugEvents, unrefHotplugEvents } from 'usb';
138
139
// Configure polling behavior
140
console.log(`Current polling delay: ${usb.pollHotplugDelay}ms`);
141
console.log(`Force polling: ${usb.pollHotplug}`);
142
143
// Set custom polling delay (1 second)
144
usb.pollHotplugDelay = 1000;
145
console.log(`Set polling delay to ${usb.pollHotplugDelay}ms`);
146
147
// Force use of polling instead of native hotplug events
148
usb.pollHotplug = true;
149
console.log('Forced polling mode enabled');
150
151
// Set up device monitoring
152
usb.on('attach', (device) => {
153
console.log('Device connected (detected via polling)');
154
});
155
156
usb.on('detach', (device) => {
157
console.log('Device disconnected (detected via polling)');
158
});
159
160
// Unreference hotplug events to allow process to exit
161
// even when listening for attach/detach events
162
unrefHotplugEvents();
163
console.log('Hotplug events unreferenced - process can exit');
164
165
// Re-reference if you need to keep process alive
166
setTimeout(() => {
167
refHotplugEvents();
168
console.log('Hotplug events re-referenced');
169
}, 5000);
170
171
// Example of temporary monitoring that doesn't keep process alive
172
function monitorUSBForShortTime() {
173
unrefHotplugEvents(); // Allow process to exit
174
175
const deviceHandler = (device: Device) => {
176
console.log('Device event detected');
177
// Handle device...
178
};
179
180
usb.on('attach', deviceHandler);
181
182
// Stop monitoring after 10 seconds
183
setTimeout(() => {
184
usb.off('attach', deviceHandler);
185
console.log('Stopped USB monitoring');
186
}, 10000);
187
}
188
```
189
190
### Device-Specific Event Handling
191
192
Handle events for specific devices or device types.
193
194
**Usage Examples:**
195
196
```typescript
197
import { usb } from 'usb';
198
199
// Monitor for specific vendor/product ID
200
const TARGET_VID = 0x1234;
201
const TARGET_PID = 0x5678;
202
203
usb.on('attach', (device) => {
204
const desc = device.deviceDescriptor;
205
206
if (desc.idVendor === TARGET_VID && desc.idProduct === TARGET_PID) {
207
console.log('Target device connected!');
208
handleTargetDevice(device);
209
}
210
});
211
212
usb.on('detach', (device) => {
213
const desc = device.deviceDescriptor;
214
215
if (desc.idVendor === TARGET_VID && desc.idProduct === TARGET_PID) {
216
console.log('Target device disconnected!');
217
handleTargetDeviceDisconnect(device);
218
}
219
});
220
221
function handleTargetDevice(device: Device) {
222
try {
223
device.open();
224
console.log('Target device opened for communication');
225
226
// Store device reference for later use
227
global.currentTargetDevice = device;
228
229
// Set up device communication...
230
231
} catch (error) {
232
console.error('Failed to open target device:', error);
233
}
234
}
235
236
function handleTargetDeviceDisconnect(device: Device) {
237
if (global.currentTargetDevice === device) {
238
console.log('Current target device disconnected');
239
global.currentTargetDevice = null;
240
241
// Clean up any ongoing operations
242
// The device will be automatically closed by the system
243
}
244
}
245
246
// Monitor for device class (e.g., HID devices)
247
usb.on('attach', (device) => {
248
const desc = device.deviceDescriptor;
249
250
if (desc.bDeviceClass === 3) { // HID class
251
console.log('HID device connected');
252
console.log(` VID:PID = ${desc.idVendor.toString(16)}:${desc.idProduct.toString(16)}`);
253
}
254
});
255
256
// Monitor multiple device types
257
const MONITORED_DEVICES = [
258
{ vid: 0x1234, pid: 0x5678, name: 'My Custom Device' },
259
{ vid: 0xABCD, pid: 0xEF01, name: 'Another Device' },
260
{ vid: 0x2341, pid: 0x0043, name: 'Arduino Uno' }
261
];
262
263
usb.on('attach', (device) => {
264
const desc = device.deviceDescriptor;
265
266
const knownDevice = MONITORED_DEVICES.find(d =>
267
d.vid === desc.idVendor && d.pid === desc.idProduct
268
);
269
270
if (knownDevice) {
271
console.log(`Known device connected: ${knownDevice.name}`);
272
console.log(` VID:PID = ${desc.idVendor.toString(16)}:${desc.idProduct.toString(16)}`);
273
274
// Handle specific device
275
handleKnownDevice(device, knownDevice);
276
}
277
});
278
279
function handleKnownDevice(device: Device, deviceInfo: any) {
280
console.log(`Setting up ${deviceInfo.name}...`);
281
282
try {
283
device.open();
284
285
// Device-specific setup based on deviceInfo
286
switch (deviceInfo.name) {
287
case 'Arduino Uno':
288
setupArduino(device);
289
break;
290
case 'My Custom Device':
291
setupCustomDevice(device);
292
break;
293
default:
294
setupGenericDevice(device);
295
}
296
297
} catch (error) {
298
console.error(`Failed to setup ${deviceInfo.name}:`, error);
299
}
300
}
301
```
302
303
### Event Error Handling
304
305
Handle errors and edge cases in event processing.
306
307
**Usage Examples:**
308
309
```typescript
310
import { usb } from 'usb';
311
312
// Robust event handler with error handling
313
function createRobustDeviceHandler() {
314
const connectedDevices = new Map<string, Device>();
315
316
const attachHandler = (device: Device) => {
317
try {
318
const deviceKey = `${device.deviceDescriptor.idVendor}:${device.deviceDescriptor.idProduct}:${device.busNumber}:${device.deviceAddress}`;
319
320
console.log(`Device attached: ${deviceKey}`);
321
322
// Store device reference
323
connectedDevices.set(deviceKey, device);
324
325
// Try to get device information safely
326
getDeviceInfoSafely(device)
327
.then(info => {
328
console.log('Device info:', info);
329
})
330
.catch(error => {
331
console.warn('Could not get device info:', error.message);
332
});
333
334
} catch (error) {
335
console.error('Error in attach handler:', error);
336
}
337
};
338
339
const detachHandler = (device: Device) => {
340
try {
341
const deviceKey = `${device.deviceDescriptor.idVendor}:${device.deviceDescriptor.idProduct}:${device.busNumber}:${device.deviceAddress}`;
342
343
console.log(`Device detached: ${deviceKey}`);
344
345
// Remove from tracking
346
connectedDevices.delete(deviceKey);
347
348
// Clean up any resources associated with this device
349
cleanupDeviceResources(deviceKey);
350
351
} catch (error) {
352
console.error('Error in detach handler:', error);
353
}
354
};
355
356
// Add error handling for the event system itself
357
const errorHandler = (error: Error) => {
358
console.error('USB event system error:', error);
359
360
// Optionally restart event monitoring
361
restartEventMonitoring();
362
};
363
364
usb.on('attach', attachHandler);
365
usb.on('detach', detachHandler);
366
usb.on('error', errorHandler);
367
368
return {
369
stop: () => {
370
usb.off('attach', attachHandler);
371
usb.off('detach', detachHandler);
372
usb.off('error', errorHandler);
373
connectedDevices.clear();
374
},
375
getConnectedDevices: () => Array.from(connectedDevices.values())
376
};
377
}
378
379
async function getDeviceInfoSafely(device: Device): Promise<any> {
380
return new Promise((resolve, reject) => {
381
const timeout = setTimeout(() => {
382
reject(new Error('Timeout getting device info'));
383
}, 5000);
384
385
try {
386
device.open();
387
388
const info: any = {
389
vid: device.deviceDescriptor.idVendor,
390
pid: device.deviceDescriptor.idProduct,
391
manufacturer: null,
392
product: null,
393
serial: null
394
};
395
396
let pendingStrings = 0;
397
let completedStrings = 0;
398
399
const checkComplete = () => {
400
completedStrings++;
401
if (completedStrings >= pendingStrings) {
402
clearTimeout(timeout);
403
device.close();
404
resolve(info);
405
}
406
};
407
408
// Get manufacturer string
409
if (device.deviceDescriptor.iManufacturer > 0) {
410
pendingStrings++;
411
device.getStringDescriptor(device.deviceDescriptor.iManufacturer, (error, value) => {
412
if (!error && value) info.manufacturer = value;
413
checkComplete();
414
});
415
}
416
417
// Get product string
418
if (device.deviceDescriptor.iProduct > 0) {
419
pendingStrings++;
420
device.getStringDescriptor(device.deviceDescriptor.iProduct, (error, value) => {
421
if (!error && value) info.product = value;
422
checkComplete();
423
});
424
}
425
426
// Get serial string
427
if (device.deviceDescriptor.iSerialNumber > 0) {
428
pendingStrings++;
429
device.getStringDescriptor(device.deviceDescriptor.iSerialNumber, (error, value) => {
430
if (!error && value) info.serial = value;
431
checkComplete();
432
});
433
}
434
435
// If no strings to fetch, resolve immediately
436
if (pendingStrings === 0) {
437
clearTimeout(timeout);
438
device.close();
439
resolve(info);
440
}
441
442
} catch (error) {
443
clearTimeout(timeout);
444
try { device.close(); } catch {}
445
reject(error);
446
}
447
});
448
}
449
450
function cleanupDeviceResources(deviceKey: string) {
451
console.log(`Cleaning up resources for device: ${deviceKey}`);
452
// Clean up any polling, transfers, or other resources
453
}
454
455
function restartEventMonitoring() {
456
console.log('Restarting USB event monitoring...');
457
// Implementation depends on your needs
458
}
459
460
// Usage
461
const monitor = createRobustDeviceHandler();
462
463
// Stop monitoring after some time
464
setTimeout(() => {
465
monitor.stop();
466
console.log('USB monitoring stopped');
467
}, 60000);
468
```
469
470
### ID-Based Events
471
472
Handle ID-based events when direct device objects aren't available.
473
474
```typescript { .api }
475
/**
476
* Device ID events (fallback when direct device access isn't available)
477
*/
478
interface DeviceIds {
479
idVendor: number;
480
idProduct: number;
481
}
482
```
483
484
**Usage Examples:**
485
486
```typescript
487
import { usb } from 'usb';
488
489
// Listen for ID-based events (useful for systems with limited device access)
490
usb.on('attachIds', (deviceIds) => {
491
console.log('Device IDs attached:');
492
console.log(` Vendor ID: 0x${deviceIds.idVendor.toString(16).padStart(4, '0')}`);
493
console.log(` Product ID: 0x${deviceIds.idProduct.toString(16).padStart(4, '0')}`);
494
495
// Try to find the actual device
496
const device = findByIds(deviceIds.idVendor, deviceIds.idProduct);
497
if (device) {
498
console.log('Found corresponding device object');
499
handleNewDevice(device);
500
} else {
501
console.log('Device object not accessible, using IDs only');
502
handleDeviceByIds(deviceIds);
503
}
504
});
505
506
usb.on('detachIds', (deviceIds) => {
507
console.log('Device IDs detached:');
508
console.log(` Vendor ID: 0x${deviceIds.idVendor.toString(16).padStart(4, '0')}`);
509
console.log(` Product ID: 0x${deviceIds.idProduct.toString(16).padStart(4, '0')}`);
510
511
handleDeviceDisconnectByIds(deviceIds);
512
});
513
514
function handleDeviceByIds(deviceIds: DeviceIds) {
515
console.log(`Handling device by IDs: ${deviceIds.idVendor}:${deviceIds.idProduct}`);
516
517
// Store device info for later use
518
const deviceInfo = {
519
vid: deviceIds.idVendor,
520
pid: deviceIds.idProduct,
521
connected: true,
522
lastSeen: new Date()
523
};
524
525
// Add to tracking
526
deviceRegistry.set(`${deviceIds.idVendor}:${deviceIds.idProduct}`, deviceInfo);
527
}
528
529
function handleDeviceDisconnectByIds(deviceIds: DeviceIds) {
530
const key = `${deviceIds.idVendor}:${deviceIds.idProduct}`;
531
const deviceInfo = deviceRegistry.get(key);
532
533
if (deviceInfo) {
534
deviceInfo.connected = false;
535
deviceInfo.lastSeen = new Date();
536
console.log(`Updated disconnect info for device: ${key}`);
537
}
538
}
539
540
// Device registry for tracking by IDs
541
const deviceRegistry = new Map<string, any>();
542
543
// Combine both event types for comprehensive monitoring
544
function setupComprehensiveMonitoring() {
545
// Direct device events (preferred)
546
usb.on('attach', (device) => {
547
console.log('Direct device attach event');
548
handleDirectDeviceAttach(device);
549
});
550
551
usb.on('detach', (device) => {
552
console.log('Direct device detach event');
553
handleDirectDeviceDetach(device);
554
});
555
556
// ID-based events (fallback)
557
usb.on('attachIds', (deviceIds) => {
558
console.log('ID-based attach event');
559
// Only handle if we didn't get a direct event
560
setTimeout(() => {
561
if (!wasHandledDirectly(deviceIds)) {
562
handleDeviceByIds(deviceIds);
563
}
564
}, 100);
565
});
566
567
usb.on('detachIds', (deviceIds) => {
568
console.log('ID-based detach event');
569
// Only handle if we didn't get a direct event
570
setTimeout(() => {
571
if (!wasHandledDirectly(deviceIds)) {
572
handleDeviceDisconnectByIds(deviceIds);
573
}
574
}, 100);
575
});
576
}
577
578
const recentDirectEvents = new Set<string>();
579
580
function handleDirectDeviceAttach(device: Device) {
581
const key = `${device.deviceDescriptor.idVendor}:${device.deviceDescriptor.idProduct}`;
582
recentDirectEvents.add(key);
583
584
// Remove from recent events after short delay
585
setTimeout(() => recentDirectEvents.delete(key), 500);
586
587
// Handle device normally
588
console.log('Handling direct device attach');
589
}
590
591
function handleDirectDeviceDetach(device: Device) {
592
const key = `${device.deviceDescriptor.idVendor}:${device.deviceDescriptor.idProduct}`;
593
recentDirectEvents.add(key);
594
595
// Remove from recent events after short delay
596
setTimeout(() => recentDirectEvents.delete(key), 500);
597
598
// Handle device disconnect
599
console.log('Handling direct device detach');
600
}
601
602
function wasHandledDirectly(deviceIds: DeviceIds): boolean {
603
const key = `${deviceIds.idVendor}:${deviceIds.idProduct}`;
604
return recentDirectEvents.has(key);
605
}
606
```