@mcesystems/usb-device-listener 1.0.30 → 1.0.31
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/build/Release/usb_device_listener.node +0 -0
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/native/addon.cc +1 -0
- package/native/usb_listener_common.h +2 -1
- package/native/usb_listener_mac.cc +77 -6
- package/native/usb_listener_win.cc +82 -6
- package/native/usb_listener_win.h +1 -0
- package/package.json +1 -1
|
Binary file
|
package/dist/types.d.ts
CHANGED
|
@@ -8,10 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export interface DeviceInfo {
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* Device identifier
|
|
12
|
+
* For devices with USB serial numbers: the serial number (formatted with dash for iOS style)
|
|
13
|
+
* For devices without serial: platform-specific format "USB\VID_xxxx&PID_xxxx\..."
|
|
14
|
+
* iOS devices: formatted as "XXXXXXXX-XXXXXXXXXXXXXXXX" (e.g., "00008030-001234567890402E")
|
|
13
15
|
*/
|
|
14
16
|
deviceId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Raw USB serial number (without formatting)
|
|
19
|
+
* Empty string if the device doesn't expose a serial number
|
|
20
|
+
*/
|
|
21
|
+
serialNumber: string;
|
|
15
22
|
/**
|
|
16
23
|
* USB Vendor ID (decimal)
|
|
17
24
|
* Use .toString(16) to convert to hex string
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAEhC;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAE7C;;;;;;;;OAQG;IACH,aAAa,IAAI,IAAI,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE/C;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAErD;;;;;;;;;;;;;;OAcG;IACH,WAAW,IAAI,UAAU,EAAE,CAAC;CAC5B"}
|
package/native/addon.cc
CHANGED
|
@@ -97,6 +97,7 @@ Napi::Value ListDevices(const Napi::CallbackInfo& info) {
|
|
|
97
97
|
for (size_t i = 0; i < devices.size(); i++) {
|
|
98
98
|
Napi::Object obj = Napi::Object::New(env);
|
|
99
99
|
obj.Set("deviceId", Napi::String::New(env, devices[i].deviceId));
|
|
100
|
+
obj.Set("serialNumber", Napi::String::New(env, devices[i].serialNumber));
|
|
100
101
|
obj.Set("vid", Napi::Number::New(env, devices[i].vid));
|
|
101
102
|
obj.Set("pid", Napi::Number::New(env, devices[i].pid));
|
|
102
103
|
obj.Set("locationInfo", Napi::String::New(env, devices[i].locationInfo));
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* Platform-agnostic: used by both Windows and macOS implementations
|
|
15
15
|
*/
|
|
16
16
|
struct DeviceInfo {
|
|
17
|
-
std::string deviceId; //
|
|
17
|
+
std::string deviceId; // Device ID (serial number if available, otherwise platform-specific)
|
|
18
|
+
std::string serialNumber; // USB serial number (raw, without formatting)
|
|
18
19
|
uint16_t vid{0}; // Vendor ID
|
|
19
20
|
uint16_t pid{0}; // Product ID
|
|
20
21
|
std::string locationInfo; // Physical port location (platform-specific format)
|
|
@@ -25,6 +25,66 @@ static const char* GetUSBDeviceClassName() {
|
|
|
25
25
|
return kIOUSBDeviceClassName;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Get the USB serial number from the IOKit service
|
|
30
|
+
* Returns empty string if serial number is not available
|
|
31
|
+
*/
|
|
32
|
+
static std::string GetSerialNumber(io_service_t service) {
|
|
33
|
+
// Try kUSBSerialNumberString first (standard USB property)
|
|
34
|
+
CFStringRef serialRef = (CFStringRef)IORegistryEntryCreateCFProperty(
|
|
35
|
+
service, CFSTR(kUSBSerialNumberString), kCFAllocatorDefault, 0);
|
|
36
|
+
|
|
37
|
+
// Fallback to "USB Serial Number" property name
|
|
38
|
+
if (!serialRef) {
|
|
39
|
+
serialRef = (CFStringRef)IORegistryEntryCreateCFProperty(
|
|
40
|
+
service, CFSTR("USB Serial Number"), kCFAllocatorDefault, 0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!serialRef) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Convert CFString to std::string
|
|
48
|
+
CFIndex length = CFStringGetLength(serialRef);
|
|
49
|
+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
|
|
50
|
+
std::string result(maxSize, '\0');
|
|
51
|
+
|
|
52
|
+
if (CFStringGetCString(serialRef, &result[0], maxSize, kCFStringEncodingUTF8)) {
|
|
53
|
+
result.resize(strlen(result.c_str()));
|
|
54
|
+
} else {
|
|
55
|
+
result.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
CFRelease(serialRef);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Format serial number in iOS-style dash-separated format
|
|
64
|
+
* iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
|
|
65
|
+
* Example: 00008030-001234567890402E
|
|
66
|
+
*/
|
|
67
|
+
static std::string FormatSerialAsDeviceId(const std::string& serial) {
|
|
68
|
+
if (serial.empty()) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// iOS serial numbers are typically 24-25 chars without dashes
|
|
73
|
+
// Format: first 8 chars, dash, remaining chars
|
|
74
|
+
// If it already contains dashes, return as-is
|
|
75
|
+
if (serial.find('-') != std::string::npos) {
|
|
76
|
+
return serial;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// iOS UDID format: 8 chars + dash + rest (typically 16 chars)
|
|
80
|
+
if (serial.length() >= 24) {
|
|
81
|
+
return serial.substr(0, 8) + "-" + serial.substr(8);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// For shorter serials (like Android), return as-is
|
|
85
|
+
return serial;
|
|
86
|
+
}
|
|
87
|
+
|
|
28
88
|
USBListener::USBListener() = default;
|
|
29
89
|
|
|
30
90
|
USBListener::~USBListener() {
|
|
@@ -229,6 +289,7 @@ void USBListener::HandleDeviceAdded(io_iterator_t iterator) {
|
|
|
229
289
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
230
290
|
Napi::Object obj = Napi::Object::New(env);
|
|
231
291
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
292
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
232
293
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
233
294
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
234
295
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -280,6 +341,7 @@ void USBListener::HandleDeviceRemoved(io_iterator_t iterator) {
|
|
|
280
341
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
281
342
|
Napi::Object obj = Napi::Object::New(env);
|
|
282
343
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
344
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
283
345
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
284
346
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
285
347
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -336,12 +398,21 @@ bool USBListener::GetDeviceInfoFromService(io_service_t service, DeviceInfo& inf
|
|
|
336
398
|
// Get Location ID (used for physical port identification)
|
|
337
399
|
info.locationInfo = GetLocationId(service);
|
|
338
400
|
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
info.
|
|
401
|
+
// Get USB serial number
|
|
402
|
+
info.serialNumber = GetSerialNumber(service);
|
|
403
|
+
|
|
404
|
+
// Use serial number as device ID if available (formatted for iOS style)
|
|
405
|
+
// Otherwise fall back to VID/PID/location combination
|
|
406
|
+
if (!info.serialNumber.empty()) {
|
|
407
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
408
|
+
} else {
|
|
409
|
+
// Fallback: Create a unique device ID combining VID, PID, and location
|
|
410
|
+
std::ostringstream ss;
|
|
411
|
+
ss << "USB\\VID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.vid
|
|
412
|
+
<< "&PID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.pid
|
|
413
|
+
<< "\\" << info.locationInfo;
|
|
414
|
+
info.deviceId = ss.str();
|
|
415
|
+
}
|
|
345
416
|
|
|
346
417
|
return true;
|
|
347
418
|
}
|
|
@@ -187,6 +187,7 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
|
|
|
187
187
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
188
188
|
Napi::Object obj = Napi::Object::New(env);
|
|
189
189
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
190
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
190
191
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
191
192
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
192
193
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -203,6 +204,7 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
|
|
|
203
204
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
204
205
|
Napi::Object obj = Napi::Object::New(env);
|
|
205
206
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
207
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
206
208
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
207
209
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
208
210
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -267,9 +269,9 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
|
|
|
267
269
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
268
270
|
if (_wcsicmp(instanceId, deviceIdW.c_str()) == 0) {
|
|
269
271
|
std::wstring idW = instanceId;
|
|
270
|
-
|
|
272
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
271
273
|
|
|
272
|
-
if (!GetVidPid(
|
|
274
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
273
275
|
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
|
274
276
|
return false;
|
|
275
277
|
}
|
|
@@ -280,6 +282,14 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
|
|
|
280
282
|
return false;
|
|
281
283
|
}
|
|
282
284
|
|
|
285
|
+
// Get serial number and use as device ID if available
|
|
286
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
287
|
+
if (!info.serialNumber.empty()) {
|
|
288
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
289
|
+
} else {
|
|
290
|
+
info.deviceId = fullDeviceId;
|
|
291
|
+
}
|
|
292
|
+
|
|
283
293
|
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
|
284
294
|
return true;
|
|
285
295
|
}
|
|
@@ -324,6 +334,55 @@ bool USBListener::GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t
|
|
|
324
334
|
}
|
|
325
335
|
}
|
|
326
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Get USB serial number from device instance ID
|
|
339
|
+
* The serial number is typically the last part after the last backslash
|
|
340
|
+
*/
|
|
341
|
+
std::string USBListener::GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData) {
|
|
342
|
+
WCHAR instanceId[MAX_PATH];
|
|
343
|
+
if (!SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
std::wstring idW = instanceId;
|
|
348
|
+
std::string deviceId(idW.begin(), idW.end());
|
|
349
|
+
|
|
350
|
+
// The serial number is typically after the last backslash in the device instance ID
|
|
351
|
+
// Format: USB\VID_XXXX&PID_XXXX\SERIAL_NUMBER
|
|
352
|
+
size_t lastBackslash = deviceId.rfind('\\');
|
|
353
|
+
if (lastBackslash != std::string::npos && lastBackslash + 1 < deviceId.length()) {
|
|
354
|
+
return deviceId.substr(lastBackslash + 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return "";
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Format serial number in iOS-style dash-separated format
|
|
362
|
+
* iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
|
|
363
|
+
* Example: 00008030-001234567890402E
|
|
364
|
+
*/
|
|
365
|
+
static std::string FormatSerialAsDeviceId(const std::string& serial) {
|
|
366
|
+
if (serial.empty()) {
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// iOS serial numbers are typically 24-25 chars without dashes
|
|
371
|
+
// Format: first 8 chars, dash, remaining chars
|
|
372
|
+
// If it already contains dashes, return as-is
|
|
373
|
+
if (serial.find('-') != std::string::npos) {
|
|
374
|
+
return serial;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// iOS UDID format: 8 chars + dash + rest (typically 16 chars)
|
|
378
|
+
if (serial.length() >= 24) {
|
|
379
|
+
return serial.substr(0, 8) + "-" + serial.substr(8);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// For shorter serials (like Android), return as-is
|
|
383
|
+
return serial;
|
|
384
|
+
}
|
|
385
|
+
|
|
327
386
|
void USBListener::EnumerateConnectedDevices() {
|
|
328
387
|
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
329
388
|
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
|
|
@@ -338,9 +397,9 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
338
397
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
339
398
|
DeviceInfo info;
|
|
340
399
|
std::wstring idW = instanceId;
|
|
341
|
-
|
|
400
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
342
401
|
|
|
343
|
-
if (!GetVidPid(
|
|
402
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
344
403
|
continue;
|
|
345
404
|
}
|
|
346
405
|
|
|
@@ -349,6 +408,14 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
349
408
|
continue;
|
|
350
409
|
}
|
|
351
410
|
|
|
411
|
+
// Get serial number and use as device ID if available
|
|
412
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
413
|
+
if (!info.serialNumber.empty()) {
|
|
414
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
415
|
+
} else {
|
|
416
|
+
info.deviceId = fullDeviceId;
|
|
417
|
+
}
|
|
418
|
+
|
|
352
419
|
// Cache device with exclusive lock
|
|
353
420
|
{
|
|
354
421
|
std::unique_lock lock(m_cacheMutex);
|
|
@@ -359,6 +426,7 @@ void USBListener::EnumerateConnectedDevices() {
|
|
|
359
426
|
auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
|
|
360
427
|
Napi::Object obj = Napi::Object::New(env);
|
|
361
428
|
obj.Set("deviceId", Napi::String::New(env, data->deviceId));
|
|
429
|
+
obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
|
|
362
430
|
obj.Set("vid", Napi::Number::New(env, data->vid));
|
|
363
431
|
obj.Set("pid", Napi::Number::New(env, data->pid));
|
|
364
432
|
obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
|
|
@@ -394,9 +462,9 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
|
|
|
394
462
|
if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
|
|
395
463
|
DeviceInfo info;
|
|
396
464
|
std::wstring idW = instanceId;
|
|
397
|
-
|
|
465
|
+
std::string fullDeviceId = std::string(idW.begin(), idW.end());
|
|
398
466
|
|
|
399
|
-
if (!GetVidPid(
|
|
467
|
+
if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
|
|
400
468
|
continue;
|
|
401
469
|
}
|
|
402
470
|
|
|
@@ -405,6 +473,14 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
|
|
|
405
473
|
continue;
|
|
406
474
|
}
|
|
407
475
|
|
|
476
|
+
// Get serial number and use as device ID if available
|
|
477
|
+
info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
|
|
478
|
+
if (!info.serialNumber.empty()) {
|
|
479
|
+
info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
|
|
480
|
+
} else {
|
|
481
|
+
info.deviceId = fullDeviceId;
|
|
482
|
+
}
|
|
483
|
+
|
|
408
484
|
devices.push_back(info);
|
|
409
485
|
}
|
|
410
486
|
}
|
|
@@ -74,6 +74,7 @@ private:
|
|
|
74
74
|
bool GetDeviceInfo(const std::string& devicePath, DeviceInfo& info); // Get device info from Windows API (for connections)
|
|
75
75
|
bool GetDeviceInfoFromPath(const std::string& devicePath, DeviceInfo& info); // Parse device info from path (for disconnections)
|
|
76
76
|
bool GetLocationInfo(DEVINST devInst, std::string& locationInfo); // Get physical port location
|
|
77
|
+
std::string GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData); // Get USB serial number
|
|
77
78
|
bool GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t& pid); // Parse VID/PID from device ID string
|
|
78
79
|
void EnumerateConnectedDevices(); // Enumerate devices on startup
|
|
79
80
|
|