@mcesystems/usb-device-listener 1.0.72 β 1.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +491 -491
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,491 +1,491 @@
|
|
|
1
|
-
# USB Device Listener
|
|
2
|
-
|
|
3
|
-
A high-performance native Node.js addon for monitoring USB device connections and disconnections on Windows and macOS. Built with N-API for stability across Node.js versions.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- β‘ **Real-time monitoring** - Immediate notification of USB device events via native OS APIs
|
|
8
|
-
- π― **Device filtering** - Monitor specific devices by VID/PID or hub location
|
|
9
|
-
- π **Physical port mapping** - Map USB ports to logical port numbers for consistent device identification
|
|
10
|
-
- π§΅ **Thread-safe** - Runs in separate thread without blocking Node.js event loop
|
|
11
|
-
- πͺ **Production-ready** - Memory-safe, handles multiple simultaneous device changes
|
|
12
|
-
- π **Device enumeration** - List all currently connected devices
|
|
13
|
-
- π₯οΈ **Cross-platform** - Supports both Windows and macOS
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Requirements
|
|
22
|
-
|
|
23
|
-
- **Node.js**: v18+ (ESM support required)
|
|
24
|
-
|
|
25
|
-
#### Windows
|
|
26
|
-
- **OS**: Windows 10/11
|
|
27
|
-
- **Build tools**: Visual Studio 2022 with C++ build tools
|
|
28
|
-
|
|
29
|
-
#### macOS
|
|
30
|
-
- **OS**: macOS 10.15 (Catalina) or later
|
|
31
|
-
- **Build tools**: Xcode Command Line Tools (`xcode-select --install`)
|
|
32
|
-
|
|
33
|
-
## Quick Start
|
|
34
|
-
|
|
35
|
-
Examples below use `@mcesystems/tool-debug-g4` for formatted logs.
|
|
36
|
-
|
|
37
|
-
```javascript
|
|
38
|
-
import usbListener from "usb-device-listener";
|
|
39
|
-
import {
|
|
40
|
-
logDataObject,
|
|
41
|
-
logErrorObject,
|
|
42
|
-
logHeader,
|
|
43
|
-
logNamespace,
|
|
44
|
-
setLogLevel
|
|
45
|
-
} from "@mcesystems/tool-debug-g4";
|
|
46
|
-
|
|
47
|
-
logNamespace("usb");
|
|
48
|
-
setLogLevel("debug");
|
|
49
|
-
|
|
50
|
-
// Define configuration
|
|
51
|
-
const config = {
|
|
52
|
-
logicalPortMap: {
|
|
53
|
-
// Windows format: "Port_#0005.Hub_#0002"
|
|
54
|
-
// macOS format: "Port_#14200000"
|
|
55
|
-
"Port_#0005.Hub_#0002": 1, // Map physical port to logical port 1
|
|
56
|
-
"Port_#0006.Hub_#0002": 2
|
|
57
|
-
},
|
|
58
|
-
targetDevices: [
|
|
59
|
-
{ vid: "04E8", pid: "6860" } // Only monitor Samsung devices
|
|
60
|
-
],
|
|
61
|
-
ignoredHubs: [],
|
|
62
|
-
listenOnlyHubs: []
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
function handleDeviceAdd(device) {
|
|
66
|
-
logDataObject("Device connected", {
|
|
67
|
-
locationInfo: device.locationInfo,
|
|
68
|
-
vid: device.vid.toString(16).toUpperCase().padStart(4, "0"),
|
|
69
|
-
pid: device.pid.toString(16).toUpperCase().padStart(4, "0"),
|
|
70
|
-
logicalPort: device.logicalPort ?? "<unmapped>"
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function handleDeviceRemove(device) {
|
|
75
|
-
logDataObject("Device disconnected", {
|
|
76
|
-
locationInfo: device.locationInfo,
|
|
77
|
-
logicalPort: device.logicalPort ?? "<unmapped>"
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Register event handlers
|
|
82
|
-
usbListener.onDeviceAdd(handleDeviceAdd);
|
|
83
|
-
usbListener.onDeviceRemove(handleDeviceRemove);
|
|
84
|
-
|
|
85
|
-
// Start listening
|
|
86
|
-
try {
|
|
87
|
-
usbListener.startListening(config);
|
|
88
|
-
logHeader("Listening for USB events");
|
|
89
|
-
} catch (error) {
|
|
90
|
-
logErrorObject(error, "Failed to start");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Graceful shutdown
|
|
94
|
-
process.on('SIGINT', () => {
|
|
95
|
-
logHeader("Stopping USB listener");
|
|
96
|
-
usbListener.stopListening();
|
|
97
|
-
process.exit(0);
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
Example output:
|
|
101
|
-
```
|
|
102
|
-
usb:info ================================================================================
|
|
103
|
-
usb:info Listening for USB events
|
|
104
|
-
usb:info ================================================================================
|
|
105
|
-
usb:detail ================================================================================
|
|
106
|
-
usb:detail *Device connected* handleDeviceAdd
|
|
107
|
-
usb:detail ================================================================================
|
|
108
|
-
usb:detail
|
|
109
|
-
usb:detail # locationInfo: Port_#0005.Hub_#0002
|
|
110
|
-
usb:detail # vid: 04E8
|
|
111
|
-
usb:detail # pid: 6860
|
|
112
|
-
usb:detail # logicalPort: 1
|
|
113
|
-
usb:detail
|
|
114
|
-
usb:detail ================================================================================
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## API Reference
|
|
118
|
-
|
|
119
|
-
### `startListening(config)`
|
|
120
|
-
|
|
121
|
-
Start monitoring USB device events.
|
|
122
|
-
|
|
123
|
-
**Parameters:**
|
|
124
|
-
- `config` (Object): Configuration object
|
|
125
|
-
- `logicalPortMap` (Object, optional): Map physical locations to logical port numbers
|
|
126
|
-
- Key: Location string (platform-specific format)
|
|
127
|
-
- Value: Logical port number (integer)
|
|
128
|
-
- `targetDevices` (Array, optional): Filter specific devices by VID/PID
|
|
129
|
-
- Each element: `{ vid: string, pid: string }` (hex strings, e.g., "04E8")
|
|
130
|
-
- Empty array = monitor all devices
|
|
131
|
-
- `ignoredHubs` (Array, optional): Hub location strings to ignore
|
|
132
|
-
- `listenOnlyHubs` (Array, optional): Only monitor these hub locations
|
|
133
|
-
|
|
134
|
-
**Throws:**
|
|
135
|
-
- `TypeError` if config is not an object
|
|
136
|
-
- `Error` if listener is already running
|
|
137
|
-
|
|
138
|
-
**Example:**
|
|
139
|
-
```javascript
|
|
140
|
-
usbListener.startListening({
|
|
141
|
-
logicalPortMap: {
|
|
142
|
-
"Port_#0005.Hub_#0002": 1 // Windows
|
|
143
|
-
// "Port_#14200000": 1 // macOS
|
|
144
|
-
},
|
|
145
|
-
targetDevices: [], // Monitor all devices
|
|
146
|
-
ignoredHubs: ["Port_#0001.Hub_#0001"], // Ignore this hub
|
|
147
|
-
listenOnlyHubs: [] // No restriction
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### `stopListening()`
|
|
152
|
-
|
|
153
|
-
Stop monitoring and clean up resources. Safe to call multiple times.
|
|
154
|
-
|
|
155
|
-
**Example:**
|
|
156
|
-
```javascript
|
|
157
|
-
usbListener.stopListening();
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### `updateConfig(config)`
|
|
161
|
-
|
|
162
|
-
Update the listener config at runtime. When listening, subsequent device events and `listDevices()` use the new config. When not listening, only `listDevices()` uses it until the next `startListening()`. Config is fully replaced (use `getCurrentConfig()` to merge).
|
|
163
|
-
|
|
164
|
-
**Parameters:**
|
|
165
|
-
- `config` (Object): Same shape as `startListening(config)` (logicalPortMap, targetDevices, ignoredDevices, listenOnlyDevices)
|
|
166
|
-
|
|
167
|
-
**Throws:**
|
|
168
|
-
- `TypeError` if config is not an object
|
|
169
|
-
|
|
170
|
-
**Example:**
|
|
171
|
-
```javascript
|
|
172
|
-
// Replace config entirely
|
|
173
|
-
usbListener.updateConfig({ logicalPortMap: newMap, targetDevices: [] });
|
|
174
|
-
|
|
175
|
-
// Merge with current config
|
|
176
|
-
usbListener.updateConfig({ ...usbListener.getCurrentConfig(), logicalPortMap: newMap });
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### `getCurrentConfig()`
|
|
180
|
-
|
|
181
|
-
Return a copy of the current config. Mutating the returned object does not affect the listener's internal config. Use with `updateConfig()` to merge partial changes.
|
|
182
|
-
|
|
183
|
-
**Returns:** Shallow copy of the current config object (ListenerConfig)
|
|
184
|
-
|
|
185
|
-
**Example:**
|
|
186
|
-
```javascript
|
|
187
|
-
const current = usbListener.getCurrentConfig();
|
|
188
|
-
usbListener.updateConfig({
|
|
189
|
-
...current,
|
|
190
|
-
targetDevices: [...(current.targetDevices ?? []), { vid: "04E8", pid: "6860" }]
|
|
191
|
-
});
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### `onDeviceAdd(callback)`
|
|
195
|
-
|
|
196
|
-
Register callback for device connection events.
|
|
197
|
-
|
|
198
|
-
**Parameters:**
|
|
199
|
-
- `callback` (Function): Called when device is connected
|
|
200
|
-
- `deviceInfo` (Object):
|
|
201
|
-
- `deviceId` (string): Platform-specific device instance ID
|
|
202
|
-
- `vid` (number): Vendor ID (decimal)
|
|
203
|
-
- `pid` (number): Product ID (decimal)
|
|
204
|
-
- `locationInfo` (string): Physical port location (platform-specific format)
|
|
205
|
-
- `logicalPort` (number|null): Mapped logical port or null
|
|
206
|
-
|
|
207
|
-
**Example:**
|
|
208
|
-
```javascript
|
|
209
|
-
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
210
|
-
|
|
211
|
-
usbListener.onDeviceAdd((device) => {
|
|
212
|
-
const vidHex = device.vid.toString(16).toUpperCase().padStart(4, "0");
|
|
213
|
-
const pidHex = device.pid.toString(16).toUpperCase().padStart(4, "0");
|
|
214
|
-
logDataObject("Device connected", {
|
|
215
|
-
device: `${vidHex}:${pidHex}`,
|
|
216
|
-
port: device.logicalPort ?? "<unmapped>"
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### `onDeviceRemove(callback)`
|
|
222
|
-
|
|
223
|
-
Register callback for device disconnection events. Device info format same as `onDeviceAdd`.
|
|
224
|
-
|
|
225
|
-
**Example:**
|
|
226
|
-
```javascript
|
|
227
|
-
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
228
|
-
|
|
229
|
-
usbListener.onDeviceRemove((device) => {
|
|
230
|
-
logDataObject("Device disconnected", {
|
|
231
|
-
port: device.logicalPort ?? "<unmapped>"
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### `listDevices()`
|
|
237
|
-
|
|
238
|
-
Get list of all currently connected USB devices.
|
|
239
|
-
|
|
240
|
-
**Returns:** Array of device objects (same format as callback parameter)
|
|
241
|
-
|
|
242
|
-
**Example:**
|
|
243
|
-
```javascript
|
|
244
|
-
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
245
|
-
|
|
246
|
-
const devices = usbListener.listDevices();
|
|
247
|
-
devices.forEach((device) => {
|
|
248
|
-
logDataObject("Device", {
|
|
249
|
-
locationInfo: device.locationInfo,
|
|
250
|
-
vid: device.vid.toString(16).toUpperCase().padStart(4, "0"),
|
|
251
|
-
pid: device.pid.toString(16).toUpperCase().padStart(4, "0")
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## Platform-Specific Details
|
|
257
|
-
|
|
258
|
-
### Physical Port Location
|
|
259
|
-
|
|
260
|
-
Each platform assigns unique location strings to USB ports:
|
|
261
|
-
|
|
262
|
-
#### Windows
|
|
263
|
-
- **Format**: `Port_#XXXX.Hub_#YYYY`
|
|
264
|
-
- **Example**: `Port_#0005.Hub_#0002`
|
|
265
|
-
- **Source**: Windows Device Management API
|
|
266
|
-
|
|
267
|
-
#### macOS
|
|
268
|
-
- **Format**: `Port_#XXXXXXXX` (hexadecimal location ID)
|
|
269
|
-
- **Example**: `Port_#14200000`
|
|
270
|
-
- **Source**: IOKit locationID property (encodes bus/port path)
|
|
271
|
-
|
|
272
|
-
### Getting Location Strings
|
|
273
|
-
|
|
274
|
-
Use the included `list-devices.js` utility:
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
node list-devices.js
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**Windows output:**
|
|
281
|
-
```
|
|
282
|
-
Device 1:
|
|
283
|
-
Device ID: USB\VID_04E8&PID_6860\R58NC2971AJ
|
|
284
|
-
VID: 0x04E8
|
|
285
|
-
PID: 0x6860
|
|
286
|
-
Location Info (mapping key): Port_#0005.Hub_#0002
|
|
287
|
-
|
|
288
|
-
Device 2:
|
|
289
|
-
Device ID: USB\VID_27C6&PID_6594\UID0014C59F
|
|
290
|
-
VID: 0x27C6
|
|
291
|
-
PID: 0x6594
|
|
292
|
-
Location Info (mapping key): Port_#0007.Hub_#0002
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
**macOS output:**
|
|
296
|
-
```
|
|
297
|
-
Device 1:
|
|
298
|
-
Device ID: USB\VID_04E8&PID_6860\Port_#14200000
|
|
299
|
-
VID: 0x04E8
|
|
300
|
-
PID: 0x6860
|
|
301
|
-
Location Info (mapping key): Port_#14200000
|
|
302
|
-
|
|
303
|
-
Device 2:
|
|
304
|
-
Device ID: USB\VID_05AC&PID_8262\Port_#14100000
|
|
305
|
-
VID: 0x05AC
|
|
306
|
-
PID: 0x8262
|
|
307
|
-
Location Info (mapping key): Port_#14100000
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
Copy the "Location Info" values to use in your `logicalPortMap`.
|
|
311
|
-
|
|
312
|
-
### Device Filtering
|
|
313
|
-
|
|
314
|
-
**By VID/PID:**
|
|
315
|
-
```javascript
|
|
316
|
-
targetDevices: [
|
|
317
|
-
{ vid: "2341", pid: "0043" }, // Arduino Uno
|
|
318
|
-
{ vid: "0483", pid: "5740" } // STM32
|
|
319
|
-
]
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**By Hub:**
|
|
323
|
-
```javascript
|
|
324
|
-
listenOnlyHubs: ["Hub_#0002"] // Only monitor this hub (Windows)
|
|
325
|
-
// or
|
|
326
|
-
ignoredHubs: ["Hub_#0001"] // Ignore this hub
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
## Performance & Scalability
|
|
330
|
-
|
|
331
|
-
### Handling Many Devices
|
|
332
|
-
|
|
333
|
-
The listener is designed to handle multiple simultaneous device events efficiently:
|
|
334
|
-
|
|
335
|
-
β
**Thread-safe**: Device cache protected by mutex
|
|
336
|
-
β
**Non-blocking**: Runs in separate thread, doesn't block Node.js
|
|
337
|
-
β
**Efficient**: Only processes filtered devices
|
|
338
|
-
β
**Memory-safe**: Automatic cleanup on disconnect
|
|
339
|
-
|
|
340
|
-
### Tested Scenarios
|
|
341
|
-
|
|
342
|
-
- Multiple rapid connect/disconnect cycles
|
|
343
|
-
- Simultaneous connection of 10+ devices
|
|
344
|
-
- Hub with many devices
|
|
345
|
-
- Long-running processes (hours/days)
|
|
346
|
-
|
|
347
|
-
### Best Practices
|
|
348
|
-
|
|
349
|
-
1. **Use device filtering** when possible to reduce CPU usage:
|
|
350
|
-
```javascript
|
|
351
|
-
targetDevices: [{ vid: "04E8", pid: "6860" }] // Better than monitoring all
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
2. **Keep callbacks fast** - offload heavy processing:
|
|
355
|
-
```javascript
|
|
356
|
-
onDeviceAdd((device) => {
|
|
357
|
-
// Good: Quick database write
|
|
358
|
-
db.logConnection(device);
|
|
359
|
-
|
|
360
|
-
// Bad: Long synchronous operation
|
|
361
|
-
// processLargeFile(device); // Use setTimeout or worker thread instead
|
|
362
|
-
});
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
3. **Handle errors gracefully**:
|
|
366
|
-
```javascript
|
|
367
|
-
onDeviceAdd((device) => {
|
|
368
|
-
try {
|
|
369
|
-
processDevice(device);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
console.error('Device processing failed:', error);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
4. **Clean shutdown**:
|
|
377
|
-
```javascript
|
|
378
|
-
process.on('SIGINT', () => {
|
|
379
|
-
usbListener.stopListening();
|
|
380
|
-
// Wait briefly for cleanup
|
|
381
|
-
setTimeout(() => process.exit(0), 100);
|
|
382
|
-
});
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
## Architecture
|
|
386
|
-
|
|
387
|
-
### Windows
|
|
388
|
-
|
|
389
|
-
```
|
|
390
|
-
βββββββββββββββββββββββββββββββββββββββββββ
|
|
391
|
-
β Node.js Application β
|
|
392
|
-
β βββββββββββββββββββββββββββββββββββ β
|
|
393
|
-
β β JavaScript API β β
|
|
394
|
-
β β (index.js - documented) β β
|
|
395
|
-
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
396
|
-
β β β
|
|
397
|
-
β ββββββββββββββΌβββββββββββββββββββββ β
|
|
398
|
-
β β N-API Addon (addon.cc) β β
|
|
399
|
-
β β - Converts JS β C++ types β β
|
|
400
|
-
β β - ThreadSafeFunction callbacks β β
|
|
401
|
-
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
402
|
-
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
403
|
-
β
|
|
404
|
-
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
405
|
-
β USBListener (usb_listener_win.cc) β
|
|
406
|
-
β βββββββββββββββββββββββββββββββββββββ β
|
|
407
|
-
β β Listener Thread (MessageLoop) β β
|
|
408
|
-
β β - Hidden message-only window β β
|
|
409
|
-
β β - RegisterDeviceNotification β β
|
|
410
|
-
β β - Receives WM_DEVICECHANGE β β
|
|
411
|
-
β ββββββββββββ¬βββββββββββββββββββββββββ β
|
|
412
|
-
β β β
|
|
413
|
-
β ββββββββββββΌβββββββββββββββββββββββ β
|
|
414
|
-
β β Windows Device Management API β β
|
|
415
|
-
β β - SetupDi* functions β β
|
|
416
|
-
β β - CM_Get_DevNode_* β β
|
|
417
|
-
β ββββββββββββββββββββββββββββββββββββ β
|
|
418
|
-
βββββββββββββββββββββββββββββββββββββββββββ
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
### macOS
|
|
422
|
-
|
|
423
|
-
```
|
|
424
|
-
βββββββββββββββββββββββββββββββββββββββββββ
|
|
425
|
-
β Node.js Application β
|
|
426
|
-
β βββββββββββββββββββββββββββββββββββ β
|
|
427
|
-
β β JavaScript API β β
|
|
428
|
-
β β (index.js - documented) β β
|
|
429
|
-
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
430
|
-
β β β
|
|
431
|
-
β ββββββββββββββΌβββββββββββββββββββββ β
|
|
432
|
-
β β N-API Addon (addon.cc) β β
|
|
433
|
-
β β - Converts JS β C++ types β β
|
|
434
|
-
β β - ThreadSafeFunction callbacks β β
|
|
435
|
-
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
436
|
-
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
437
|
-
β
|
|
438
|
-
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
439
|
-
β USBListener (usb_listener_mac.cc) β
|
|
440
|
-
β βββββββββββββββββββββββββββββββββββββ β
|
|
441
|
-
β β Listener Thread (CFRunLoop) β β
|
|
442
|
-
β β - IONotificationPortRef β β
|
|
443
|
-
β β - kIOFirstMatchNotification β β
|
|
444
|
-
β β - kIOTerminatedNotification β β
|
|
445
|
-
β ββββββββββββ¬βββββββββββββββββββββββββ β
|
|
446
|
-
β β β
|
|
447
|
-
β ββββββββββββΌβββββββββββββββββββββββ β
|
|
448
|
-
β β IOKit Framework β β
|
|
449
|
-
β β - IOServiceMatching β β
|
|
450
|
-
β β - IORegistryEntryCreateCFPropertyβ β
|
|
451
|
-
β ββββββββββββββββββββββββββββββββββββ β
|
|
452
|
-
βββββββββββββββββββββββββββββββββββββββββββ
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
## Troubleshooting
|
|
456
|
-
|
|
457
|
-
### Windows
|
|
458
|
-
|
|
459
|
-
**Build errors:**
|
|
460
|
-
- Ensure Visual Studio C++ build tools are installed
|
|
461
|
-
- Run from "x64 Native Tools Command Prompt"
|
|
462
|
-
|
|
463
|
-
### macOS
|
|
464
|
-
|
|
465
|
-
**Build errors:**
|
|
466
|
-
- Install Xcode Command Line Tools: `xcode-select --install`
|
|
467
|
-
- Ensure you have the latest Xcode version
|
|
468
|
-
|
|
469
|
-
**Permission issues:**
|
|
470
|
-
- USB access doesn't require special permissions on macOS
|
|
471
|
-
- If using App Sandbox, ensure proper entitlements
|
|
472
|
-
|
|
473
|
-
### General
|
|
474
|
-
|
|
475
|
-
**No events firing:**
|
|
476
|
-
- Check `targetDevices` filter isn't too restrictive
|
|
477
|
-
- Verify callbacks are registered before calling `startListening()`
|
|
478
|
-
- Use `listDevices()` to confirm devices are visible
|
|
479
|
-
|
|
480
|
-
**Incorrect location info:**
|
|
481
|
-
- Location strings are generated by the OS
|
|
482
|
-
- Different USB controllers may use different formats
|
|
483
|
-
- Always use `listDevices()` to get actual location strings
|
|
484
|
-
|
|
485
|
-
## License
|
|
486
|
-
|
|
487
|
-
MIT
|
|
488
|
-
|
|
489
|
-
## Contributing
|
|
490
|
-
|
|
491
|
-
Issues and pull requests welcome!
|
|
1
|
+
# USB Device Listener
|
|
2
|
+
|
|
3
|
+
A high-performance native Node.js addon for monitoring USB device connections and disconnections on Windows and macOS. Built with N-API for stability across Node.js versions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- β‘ **Real-time monitoring** - Immediate notification of USB device events via native OS APIs
|
|
8
|
+
- π― **Device filtering** - Monitor specific devices by VID/PID or hub location
|
|
9
|
+
- π **Physical port mapping** - Map USB ports to logical port numbers for consistent device identification
|
|
10
|
+
- π§΅ **Thread-safe** - Runs in separate thread without blocking Node.js event loop
|
|
11
|
+
- πͺ **Production-ready** - Memory-safe, handles multiple simultaneous device changes
|
|
12
|
+
- π **Device enumeration** - List all currently connected devices
|
|
13
|
+
- π₯οΈ **Cross-platform** - Supports both Windows and macOS
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Requirements
|
|
22
|
+
|
|
23
|
+
- **Node.js**: v18+ (ESM support required)
|
|
24
|
+
|
|
25
|
+
#### Windows
|
|
26
|
+
- **OS**: Windows 10/11
|
|
27
|
+
- **Build tools**: Visual Studio 2022 with C++ build tools
|
|
28
|
+
|
|
29
|
+
#### macOS
|
|
30
|
+
- **OS**: macOS 10.15 (Catalina) or later
|
|
31
|
+
- **Build tools**: Xcode Command Line Tools (`xcode-select --install`)
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
Examples below use `@mcesystems/tool-debug-g4` for formatted logs.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
import usbListener from "usb-device-listener";
|
|
39
|
+
import {
|
|
40
|
+
logDataObject,
|
|
41
|
+
logErrorObject,
|
|
42
|
+
logHeader,
|
|
43
|
+
logNamespace,
|
|
44
|
+
setLogLevel
|
|
45
|
+
} from "@mcesystems/tool-debug-g4";
|
|
46
|
+
|
|
47
|
+
logNamespace("usb");
|
|
48
|
+
setLogLevel("debug");
|
|
49
|
+
|
|
50
|
+
// Define configuration
|
|
51
|
+
const config = {
|
|
52
|
+
logicalPortMap: {
|
|
53
|
+
// Windows format: "Port_#0005.Hub_#0002"
|
|
54
|
+
// macOS format: "Port_#14200000"
|
|
55
|
+
"Port_#0005.Hub_#0002": 1, // Map physical port to logical port 1
|
|
56
|
+
"Port_#0006.Hub_#0002": 2
|
|
57
|
+
},
|
|
58
|
+
targetDevices: [
|
|
59
|
+
{ vid: "04E8", pid: "6860" } // Only monitor Samsung devices
|
|
60
|
+
],
|
|
61
|
+
ignoredHubs: [],
|
|
62
|
+
listenOnlyHubs: []
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function handleDeviceAdd(device) {
|
|
66
|
+
logDataObject("Device connected", {
|
|
67
|
+
locationInfo: device.locationInfo,
|
|
68
|
+
vid: device.vid.toString(16).toUpperCase().padStart(4, "0"),
|
|
69
|
+
pid: device.pid.toString(16).toUpperCase().padStart(4, "0"),
|
|
70
|
+
logicalPort: device.logicalPort ?? "<unmapped>"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleDeviceRemove(device) {
|
|
75
|
+
logDataObject("Device disconnected", {
|
|
76
|
+
locationInfo: device.locationInfo,
|
|
77
|
+
logicalPort: device.logicalPort ?? "<unmapped>"
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Register event handlers
|
|
82
|
+
usbListener.onDeviceAdd(handleDeviceAdd);
|
|
83
|
+
usbListener.onDeviceRemove(handleDeviceRemove);
|
|
84
|
+
|
|
85
|
+
// Start listening
|
|
86
|
+
try {
|
|
87
|
+
usbListener.startListening(config);
|
|
88
|
+
logHeader("Listening for USB events");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logErrorObject(error, "Failed to start");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Graceful shutdown
|
|
94
|
+
process.on('SIGINT', () => {
|
|
95
|
+
logHeader("Stopping USB listener");
|
|
96
|
+
usbListener.stopListening();
|
|
97
|
+
process.exit(0);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
Example output:
|
|
101
|
+
```
|
|
102
|
+
usb:info ================================================================================
|
|
103
|
+
usb:info Listening for USB events
|
|
104
|
+
usb:info ================================================================================
|
|
105
|
+
usb:detail ================================================================================
|
|
106
|
+
usb:detail *Device connected* handleDeviceAdd
|
|
107
|
+
usb:detail ================================================================================
|
|
108
|
+
usb:detail
|
|
109
|
+
usb:detail # locationInfo: Port_#0005.Hub_#0002
|
|
110
|
+
usb:detail # vid: 04E8
|
|
111
|
+
usb:detail # pid: 6860
|
|
112
|
+
usb:detail # logicalPort: 1
|
|
113
|
+
usb:detail
|
|
114
|
+
usb:detail ================================================================================
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## API Reference
|
|
118
|
+
|
|
119
|
+
### `startListening(config)`
|
|
120
|
+
|
|
121
|
+
Start monitoring USB device events.
|
|
122
|
+
|
|
123
|
+
**Parameters:**
|
|
124
|
+
- `config` (Object): Configuration object
|
|
125
|
+
- `logicalPortMap` (Object, optional): Map physical locations to logical port numbers
|
|
126
|
+
- Key: Location string (platform-specific format)
|
|
127
|
+
- Value: Logical port number (integer)
|
|
128
|
+
- `targetDevices` (Array, optional): Filter specific devices by VID/PID
|
|
129
|
+
- Each element: `{ vid: string, pid: string }` (hex strings, e.g., "04E8")
|
|
130
|
+
- Empty array = monitor all devices
|
|
131
|
+
- `ignoredHubs` (Array, optional): Hub location strings to ignore
|
|
132
|
+
- `listenOnlyHubs` (Array, optional): Only monitor these hub locations
|
|
133
|
+
|
|
134
|
+
**Throws:**
|
|
135
|
+
- `TypeError` if config is not an object
|
|
136
|
+
- `Error` if listener is already running
|
|
137
|
+
|
|
138
|
+
**Example:**
|
|
139
|
+
```javascript
|
|
140
|
+
usbListener.startListening({
|
|
141
|
+
logicalPortMap: {
|
|
142
|
+
"Port_#0005.Hub_#0002": 1 // Windows
|
|
143
|
+
// "Port_#14200000": 1 // macOS
|
|
144
|
+
},
|
|
145
|
+
targetDevices: [], // Monitor all devices
|
|
146
|
+
ignoredHubs: ["Port_#0001.Hub_#0001"], // Ignore this hub
|
|
147
|
+
listenOnlyHubs: [] // No restriction
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### `stopListening()`
|
|
152
|
+
|
|
153
|
+
Stop monitoring and clean up resources. Safe to call multiple times.
|
|
154
|
+
|
|
155
|
+
**Example:**
|
|
156
|
+
```javascript
|
|
157
|
+
usbListener.stopListening();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `updateConfig(config)`
|
|
161
|
+
|
|
162
|
+
Update the listener config at runtime. When listening, subsequent device events and `listDevices()` use the new config. When not listening, only `listDevices()` uses it until the next `startListening()`. Config is fully replaced (use `getCurrentConfig()` to merge).
|
|
163
|
+
|
|
164
|
+
**Parameters:**
|
|
165
|
+
- `config` (Object): Same shape as `startListening(config)` (logicalPortMap, targetDevices, ignoredDevices, listenOnlyDevices)
|
|
166
|
+
|
|
167
|
+
**Throws:**
|
|
168
|
+
- `TypeError` if config is not an object
|
|
169
|
+
|
|
170
|
+
**Example:**
|
|
171
|
+
```javascript
|
|
172
|
+
// Replace config entirely
|
|
173
|
+
usbListener.updateConfig({ logicalPortMap: newMap, targetDevices: [] });
|
|
174
|
+
|
|
175
|
+
// Merge with current config
|
|
176
|
+
usbListener.updateConfig({ ...usbListener.getCurrentConfig(), logicalPortMap: newMap });
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### `getCurrentConfig()`
|
|
180
|
+
|
|
181
|
+
Return a copy of the current config. Mutating the returned object does not affect the listener's internal config. Use with `updateConfig()` to merge partial changes.
|
|
182
|
+
|
|
183
|
+
**Returns:** Shallow copy of the current config object (ListenerConfig)
|
|
184
|
+
|
|
185
|
+
**Example:**
|
|
186
|
+
```javascript
|
|
187
|
+
const current = usbListener.getCurrentConfig();
|
|
188
|
+
usbListener.updateConfig({
|
|
189
|
+
...current,
|
|
190
|
+
targetDevices: [...(current.targetDevices ?? []), { vid: "04E8", pid: "6860" }]
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### `onDeviceAdd(callback)`
|
|
195
|
+
|
|
196
|
+
Register callback for device connection events.
|
|
197
|
+
|
|
198
|
+
**Parameters:**
|
|
199
|
+
- `callback` (Function): Called when device is connected
|
|
200
|
+
- `deviceInfo` (Object):
|
|
201
|
+
- `deviceId` (string): Platform-specific device instance ID
|
|
202
|
+
- `vid` (number): Vendor ID (decimal)
|
|
203
|
+
- `pid` (number): Product ID (decimal)
|
|
204
|
+
- `locationInfo` (string): Physical port location (platform-specific format)
|
|
205
|
+
- `logicalPort` (number|null): Mapped logical port or null
|
|
206
|
+
|
|
207
|
+
**Example:**
|
|
208
|
+
```javascript
|
|
209
|
+
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
210
|
+
|
|
211
|
+
usbListener.onDeviceAdd((device) => {
|
|
212
|
+
const vidHex = device.vid.toString(16).toUpperCase().padStart(4, "0");
|
|
213
|
+
const pidHex = device.pid.toString(16).toUpperCase().padStart(4, "0");
|
|
214
|
+
logDataObject("Device connected", {
|
|
215
|
+
device: `${vidHex}:${pidHex}`,
|
|
216
|
+
port: device.logicalPort ?? "<unmapped>"
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### `onDeviceRemove(callback)`
|
|
222
|
+
|
|
223
|
+
Register callback for device disconnection events. Device info format same as `onDeviceAdd`.
|
|
224
|
+
|
|
225
|
+
**Example:**
|
|
226
|
+
```javascript
|
|
227
|
+
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
228
|
+
|
|
229
|
+
usbListener.onDeviceRemove((device) => {
|
|
230
|
+
logDataObject("Device disconnected", {
|
|
231
|
+
port: device.logicalPort ?? "<unmapped>"
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `listDevices()`
|
|
237
|
+
|
|
238
|
+
Get list of all currently connected USB devices.
|
|
239
|
+
|
|
240
|
+
**Returns:** Array of device objects (same format as callback parameter)
|
|
241
|
+
|
|
242
|
+
**Example:**
|
|
243
|
+
```javascript
|
|
244
|
+
import { logDataObject } from "@mcesystems/tool-debug-g4";
|
|
245
|
+
|
|
246
|
+
const devices = usbListener.listDevices();
|
|
247
|
+
devices.forEach((device) => {
|
|
248
|
+
logDataObject("Device", {
|
|
249
|
+
locationInfo: device.locationInfo,
|
|
250
|
+
vid: device.vid.toString(16).toUpperCase().padStart(4, "0"),
|
|
251
|
+
pid: device.pid.toString(16).toUpperCase().padStart(4, "0")
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Platform-Specific Details
|
|
257
|
+
|
|
258
|
+
### Physical Port Location
|
|
259
|
+
|
|
260
|
+
Each platform assigns unique location strings to USB ports:
|
|
261
|
+
|
|
262
|
+
#### Windows
|
|
263
|
+
- **Format**: `Port_#XXXX.Hub_#YYYY`
|
|
264
|
+
- **Example**: `Port_#0005.Hub_#0002`
|
|
265
|
+
- **Source**: Windows Device Management API
|
|
266
|
+
|
|
267
|
+
#### macOS
|
|
268
|
+
- **Format**: `Port_#XXXXXXXX` (hexadecimal location ID)
|
|
269
|
+
- **Example**: `Port_#14200000`
|
|
270
|
+
- **Source**: IOKit locationID property (encodes bus/port path)
|
|
271
|
+
|
|
272
|
+
### Getting Location Strings
|
|
273
|
+
|
|
274
|
+
Use the included `list-devices.js` utility:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
node list-devices.js
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Windows output:**
|
|
281
|
+
```
|
|
282
|
+
Device 1:
|
|
283
|
+
Device ID: USB\VID_04E8&PID_6860\R58NC2971AJ
|
|
284
|
+
VID: 0x04E8
|
|
285
|
+
PID: 0x6860
|
|
286
|
+
Location Info (mapping key): Port_#0005.Hub_#0002
|
|
287
|
+
|
|
288
|
+
Device 2:
|
|
289
|
+
Device ID: USB\VID_27C6&PID_6594\UID0014C59F
|
|
290
|
+
VID: 0x27C6
|
|
291
|
+
PID: 0x6594
|
|
292
|
+
Location Info (mapping key): Port_#0007.Hub_#0002
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**macOS output:**
|
|
296
|
+
```
|
|
297
|
+
Device 1:
|
|
298
|
+
Device ID: USB\VID_04E8&PID_6860\Port_#14200000
|
|
299
|
+
VID: 0x04E8
|
|
300
|
+
PID: 0x6860
|
|
301
|
+
Location Info (mapping key): Port_#14200000
|
|
302
|
+
|
|
303
|
+
Device 2:
|
|
304
|
+
Device ID: USB\VID_05AC&PID_8262\Port_#14100000
|
|
305
|
+
VID: 0x05AC
|
|
306
|
+
PID: 0x8262
|
|
307
|
+
Location Info (mapping key): Port_#14100000
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Copy the "Location Info" values to use in your `logicalPortMap`.
|
|
311
|
+
|
|
312
|
+
### Device Filtering
|
|
313
|
+
|
|
314
|
+
**By VID/PID:**
|
|
315
|
+
```javascript
|
|
316
|
+
targetDevices: [
|
|
317
|
+
{ vid: "2341", pid: "0043" }, // Arduino Uno
|
|
318
|
+
{ vid: "0483", pid: "5740" } // STM32
|
|
319
|
+
]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**By Hub:**
|
|
323
|
+
```javascript
|
|
324
|
+
listenOnlyHubs: ["Hub_#0002"] // Only monitor this hub (Windows)
|
|
325
|
+
// or
|
|
326
|
+
ignoredHubs: ["Hub_#0001"] // Ignore this hub
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Performance & Scalability
|
|
330
|
+
|
|
331
|
+
### Handling Many Devices
|
|
332
|
+
|
|
333
|
+
The listener is designed to handle multiple simultaneous device events efficiently:
|
|
334
|
+
|
|
335
|
+
β
**Thread-safe**: Device cache protected by mutex
|
|
336
|
+
β
**Non-blocking**: Runs in separate thread, doesn't block Node.js
|
|
337
|
+
β
**Efficient**: Only processes filtered devices
|
|
338
|
+
β
**Memory-safe**: Automatic cleanup on disconnect
|
|
339
|
+
|
|
340
|
+
### Tested Scenarios
|
|
341
|
+
|
|
342
|
+
- Multiple rapid connect/disconnect cycles
|
|
343
|
+
- Simultaneous connection of 10+ devices
|
|
344
|
+
- Hub with many devices
|
|
345
|
+
- Long-running processes (hours/days)
|
|
346
|
+
|
|
347
|
+
### Best Practices
|
|
348
|
+
|
|
349
|
+
1. **Use device filtering** when possible to reduce CPU usage:
|
|
350
|
+
```javascript
|
|
351
|
+
targetDevices: [{ vid: "04E8", pid: "6860" }] // Better than monitoring all
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
2. **Keep callbacks fast** - offload heavy processing:
|
|
355
|
+
```javascript
|
|
356
|
+
onDeviceAdd((device) => {
|
|
357
|
+
// Good: Quick database write
|
|
358
|
+
db.logConnection(device);
|
|
359
|
+
|
|
360
|
+
// Bad: Long synchronous operation
|
|
361
|
+
// processLargeFile(device); // Use setTimeout or worker thread instead
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
3. **Handle errors gracefully**:
|
|
366
|
+
```javascript
|
|
367
|
+
onDeviceAdd((device) => {
|
|
368
|
+
try {
|
|
369
|
+
processDevice(device);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error('Device processing failed:', error);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
4. **Clean shutdown**:
|
|
377
|
+
```javascript
|
|
378
|
+
process.on('SIGINT', () => {
|
|
379
|
+
usbListener.stopListening();
|
|
380
|
+
// Wait briefly for cleanup
|
|
381
|
+
setTimeout(() => process.exit(0), 100);
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Architecture
|
|
386
|
+
|
|
387
|
+
### Windows
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
391
|
+
β Node.js Application β
|
|
392
|
+
β βββββββββββββββββββββββββββββββββββ β
|
|
393
|
+
β β JavaScript API β β
|
|
394
|
+
β β (index.js - documented) β β
|
|
395
|
+
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
396
|
+
β β β
|
|
397
|
+
β ββββββββββββββΌβββββββββββββββββββββ β
|
|
398
|
+
β β N-API Addon (addon.cc) β β
|
|
399
|
+
β β - Converts JS β C++ types β β
|
|
400
|
+
β β - ThreadSafeFunction callbacks β β
|
|
401
|
+
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
402
|
+
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
403
|
+
β
|
|
404
|
+
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
405
|
+
β USBListener (usb_listener_win.cc) β
|
|
406
|
+
β βββββββββββββββββββββββββββββββββββββ β
|
|
407
|
+
β β Listener Thread (MessageLoop) β β
|
|
408
|
+
β β - Hidden message-only window β β
|
|
409
|
+
β β - RegisterDeviceNotification β β
|
|
410
|
+
β β - Receives WM_DEVICECHANGE β β
|
|
411
|
+
β ββββββββββββ¬βββββββββββββββββββββββββ β
|
|
412
|
+
β β β
|
|
413
|
+
β ββββββββββββΌβββββββββββββββββββββββ β
|
|
414
|
+
β β Windows Device Management API β β
|
|
415
|
+
β β - SetupDi* functions β β
|
|
416
|
+
β β - CM_Get_DevNode_* β β
|
|
417
|
+
β ββββββββββββββββββββββββββββββββββββ β
|
|
418
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### macOS
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
425
|
+
β Node.js Application β
|
|
426
|
+
β βββββββββββββββββββββββββββββββββββ β
|
|
427
|
+
β β JavaScript API β β
|
|
428
|
+
β β (index.js - documented) β β
|
|
429
|
+
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
430
|
+
β β β
|
|
431
|
+
β ββββββββββββββΌβββββββββββββββββββββ β
|
|
432
|
+
β β N-API Addon (addon.cc) β β
|
|
433
|
+
β β - Converts JS β C++ types β β
|
|
434
|
+
β β - ThreadSafeFunction callbacks β β
|
|
435
|
+
β ββββββββββββββ¬βββββββββββββββββββββ β
|
|
436
|
+
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
437
|
+
β
|
|
438
|
+
βββββββββββββββββΌββββββββββββββββββββββββββ
|
|
439
|
+
β USBListener (usb_listener_mac.cc) β
|
|
440
|
+
β βββββββββββββββββββββββββββββββββββββ β
|
|
441
|
+
β β Listener Thread (CFRunLoop) β β
|
|
442
|
+
β β - IONotificationPortRef β β
|
|
443
|
+
β β - kIOFirstMatchNotification β β
|
|
444
|
+
β β - kIOTerminatedNotification β β
|
|
445
|
+
β ββββββββββββ¬βββββββββββββββββββββββββ β
|
|
446
|
+
β β β
|
|
447
|
+
β ββββββββββββΌβββββββββββββββββββββββ β
|
|
448
|
+
β β IOKit Framework β β
|
|
449
|
+
β β - IOServiceMatching β β
|
|
450
|
+
β β - IORegistryEntryCreateCFPropertyβ β
|
|
451
|
+
β ββββββββββββββββββββββββββββββββββββ β
|
|
452
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## Troubleshooting
|
|
456
|
+
|
|
457
|
+
### Windows
|
|
458
|
+
|
|
459
|
+
**Build errors:**
|
|
460
|
+
- Ensure Visual Studio C++ build tools are installed
|
|
461
|
+
- Run from "x64 Native Tools Command Prompt"
|
|
462
|
+
|
|
463
|
+
### macOS
|
|
464
|
+
|
|
465
|
+
**Build errors:**
|
|
466
|
+
- Install Xcode Command Line Tools: `xcode-select --install`
|
|
467
|
+
- Ensure you have the latest Xcode version
|
|
468
|
+
|
|
469
|
+
**Permission issues:**
|
|
470
|
+
- USB access doesn't require special permissions on macOS
|
|
471
|
+
- If using App Sandbox, ensure proper entitlements
|
|
472
|
+
|
|
473
|
+
### General
|
|
474
|
+
|
|
475
|
+
**No events firing:**
|
|
476
|
+
- Check `targetDevices` filter isn't too restrictive
|
|
477
|
+
- Verify callbacks are registered before calling `startListening()`
|
|
478
|
+
- Use `listDevices()` to confirm devices are visible
|
|
479
|
+
|
|
480
|
+
**Incorrect location info:**
|
|
481
|
+
- Location strings are generated by the OS
|
|
482
|
+
- Different USB controllers may use different formats
|
|
483
|
+
- Always use `listDevices()` to get actual location strings
|
|
484
|
+
|
|
485
|
+
## License
|
|
486
|
+
|
|
487
|
+
MIT
|
|
488
|
+
|
|
489
|
+
## Contributing
|
|
490
|
+
|
|
491
|
+
Issues and pull requests welcome!
|