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

event-handling.mddocs/

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

```