Discovering USB Paths
When multiple readers share the same VID/PID, you need a way to target a specific physical USB port. RIK supports connecting by topological USB path -- a stable identifier for a physical port that doesn't change when you unplug and replug a device.
This page covers how to discover USB paths on each platform.
Path Formats
| Platform | Format | Example |
|---|---|---|
| Linux | B-P or B-P.P.P (bus-port) | 1-7, 1-7.2 |
| Windows | Location path string | PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7) |
The path identifies a physical port, not a device. Moving a reader to a different port changes the path. Reconnecting the same reader to the same port keeps the path stable.
C++ UsbDeviceEnumerator
The RIK SDK ships with UsbDeviceEnumerator, a C++ utility class for discovering connected readers and their topological paths. Include the header from the installed SDK:
#include "UsbDeviceEnumerator.h"
Enumerate All Known Readers
#include <iostream>
#include <iomanip>
#include "UsbDeviceEnumerator.h"
int main() {
using namespace RikTransport;
// Get the predefined VID/PID filters for all known rf IDEAS readers
auto filters = UsbDeviceEnumerator::GetKnownReaderFilters();
// Enumerate connected devices matching those filters
auto devices = UsbDeviceEnumerator::EnumerateDevices(filters);
std::cout << "Found " << devices.size() << " reader(s):\n\n";
for (const auto& device : devices) {
std::cout << " Path: " << device.TopologicalPath << "\n"
<< " VID:PID: " << std::hex << std::setfill('0')
<< "0x" << std::setw(4) << device.VendorId << ":"
<< "0x" << std::setw(4) << device.ProductId << "\n"
<< " Name: " << device.DeviceName << "\n"
<< " HID: " << device.HidDevicePath << "\n\n";
}
return 0;
}
Enumerate a Specific VID/PID
auto devices = UsbDeviceEnumerator::EnumerateDevices(0x0C27, 0x3BFA);
for (const auto& device : devices) {
std::cout << "Reader at path: " << device.TopologicalPath << "\n";
}
Enumerate Then Connect
#include <iostream>
#include <cwchar>
#include "UsbDeviceEnumerator.h"
#include "Reader/AbstractReader.h"
#include "Reader/Reader.h"
int main() {
using namespace RikTransport;
using namespace Rik;
using namespace RikCommon;
// 1. Discover readers
auto devices = UsbDeviceEnumerator::EnumerateDevices(0x0C27, 0x3BFA);
if (devices.empty()) {
std::cerr << "No readers found.\n";
return 1;
}
// 2. Pick the first one and get its path
const auto& target = devices[0];
std::cout << "Connecting to reader at path: " << target.TopologicalPath << "\n";
// 3. Convert path to wide string and set on ReaderDefinition
std::wstring widePath(target.TopologicalPath.begin(), target.TopologicalPath.end());
ReaderDefinition readerDef{};
readerDef.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;
// UsbPath takes full precedence -- VID/PID are not required in this mode
std::wcsncpy(readerDef.DeviceId.UsbPath, widePath.c_str(),
sizeof(readerDef.DeviceId.UsbPath) / sizeof(wchar_t) - 1);
// 4. Connect
auto handle = AbstractReader::CreateReaderInstance(readerDef, 3);
auto* app = AbstractReader::GetInstance(handle);
app->Init();
auto metadata = app->GetMetadataStruct();
std::cout << "Connected: " << metadata.PartNumber << "\n";
app->Close();
AbstractReader::DestroyInstance(handle);
return 0;
}
CMake Setup
Link to the Application library as normal -- UsbDeviceEnumerator symbols are included:
find_package(ReaderIntegrationKit REQUIRED)
add_executable(enumerate_readers enumerate_readers.cpp)
target_link_libraries(enumerate_readers
PRIVATE ReaderIntegrationKit::ReaderIntegrationKit)
UsbDeviceInfo Fields
| Field | Type | Description |
|---|---|---|
TopologicalPath | std::string | Stable USB path (use as DeviceId.UsbPath) |
VendorId | uint16_t | USB Vendor ID |
ProductId | uint16_t | USB Product ID |
DeviceName | std::string | Device name from USB descriptor |
HidDevicePath | std::string | OS-level HID path (e.g., /dev/hidraw1) |
UsbDeviceEnumerator is a C++ API only. It is not available through the C API, C#, or Python facades. For non-C++ workflows, use the platform-specific methods described below.
Linux -- Shell Script
RIK includes a shell script that enumerates USB devices and displays their topological paths by reading from Linux sysfs.
Usage
# Show all known rf IDEAS readers
./enum_usb_linux.sh
# Show all USB devices (not just known ones)
./enum_usb_linux.sh --all
# Search for a specific VID:PID
./enum_usb_linux.sh 0c27:3bfa
Example Output
TOPO PATH VID:PID DEVICE NAME HIDRAW
--------------------------------------------------------------------------------
1-7 0c27:3bfa RFIDeas reader /dev/hidraw1
1-8 0c27:3bfa RFIDeas reader /dev/hidraw2
The TOPO PATH column is the value to pass as DeviceId.UsbPath. In the example above, the reader on port 1-7 would be targeted with UsbPath = "1-7".
Linux -- Manual Discovery
You can also discover paths directly from sysfs without the script:
# List all USB devices with their topological paths
for dev in /sys/bus/usb/devices/*/; do
# Skip interfaces and root hubs
[[ "$(basename "$dev")" == *:* ]] && continue
[[ "$(basename "$dev")" == usb* ]] && continue
[ -f "$dev/idVendor" ] || continue
vid=$(cat "$dev/idVendor" 2>/dev/null)
pid=$(cat "$dev/idProduct" 2>/dev/null)
bus=$(cat "$dev/busnum" 2>/dev/null)
port=$(cat "$dev/devpath" 2>/dev/null)
product=$(cat "$dev/product" 2>/dev/null)
echo "${bus}-${port} ${vid}:${pid} ${product}"
done
Filter for rf IDEAS devices:
# Show only rf IDEAS readers (VID 0c27)
for dev in /sys/bus/usb/devices/*/; do
[[ "$(basename "$dev")" == *:* ]] && continue
[ -f "$dev/idVendor" ] || continue
vid=$(cat "$dev/idVendor" 2>/dev/null)
[ "$vid" = "0c27" ] || continue
pid=$(cat "$dev/idProduct" 2>/dev/null)
bus=$(cat "$dev/busnum" 2>/dev/null)
port=$(cat "$dev/devpath" 2>/dev/null)
product=$(cat "$dev/product" 2>/dev/null)
echo "Path: ${bus}-${port} VID:PID: ${vid}:${pid} Name: ${product}"
done
Windows -- PowerShell
On Windows, use PowerShell to discover USB device location paths:
# List all USB devices with location paths
Get-PnpDevice -Class USB -Status OK |
ForEach-Object {
$device = $_
$props = Get-PnpDeviceProperty -InstanceId $device.InstanceId
$locationPaths = ($props | Where-Object KeyName -eq 'DEVPKEY_Device_LocationPaths').Data
$hardwareIds = ($props | Where-Object KeyName -eq 'DEVPKEY_Device_HardwareIds').Data
if ($locationPaths -and $hardwareIds) {
# Extract VID/PID from hardware ID string
$vidMatch = [regex]::Match($hardwareIds[0], 'VID_([0-9A-Fa-f]{4})')
$pidMatch = [regex]::Match($hardwareIds[0], 'PID_([0-9A-Fa-f]{4})')
if ($vidMatch.Success) {
[PSCustomObject]@{
LocationPath = $locationPaths[0]
VID = $vidMatch.Groups[1].Value
PID = if ($pidMatch.Success) { $pidMatch.Groups[1].Value } else { "????" }
Name = $device.FriendlyName
}
}
}
} |
Format-Table -AutoSize
Filter for rf IDEAS Devices
# Show only rf IDEAS readers (VID 0C27)
Get-PnpDevice -Class USB -Status OK |
ForEach-Object {
$props = Get-PnpDeviceProperty -InstanceId $_.InstanceId
$locationPaths = ($props | Where-Object KeyName -eq 'DEVPKEY_Device_LocationPaths').Data
$hardwareIds = ($props | Where-Object KeyName -eq 'DEVPKEY_Device_HardwareIds').Data
if ($locationPaths -and $hardwareIds -and $hardwareIds[0] -match 'VID_0C27') {
Write-Host "Path: $($locationPaths[0])"
Write-Host "Name: $($_.FriendlyName)"
Write-Host ""
}
}
Windows -- Device Manager
You can also find the location path through Device Manager:
- Open Device Manager
- Find your reader under Human Interface Devices or Universal Serial Bus devices
- Right-click > Properties > Details tab
- Select Location paths from the property dropdown
- The value (e.g.,
PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7)) is the USB path to use
Using the Discovered Path
Once you have a topological path, pass it as DeviceId.UsbPath when creating a ReaderDefinition. See Connect by USB Path for full examples in all languages.
// Linux path from enum_usb_linux.sh or UsbDeviceEnumerator
std::wstring usbPath = L"1-7";
// Windows path from PowerShell or Device Manager
// std::wstring usbPath = L"PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7)";
std::wcsncpy(readerDef.DeviceId.UsbPath, usbPath.c_str(),
sizeof(readerDef.DeviceId.UsbPath) / sizeof(wchar_t) - 1);
USB paths are stable across device reconnections and system reboots as long as the reader stays in the same physical port. They are ideal for fixed installations (kiosks, access control panels, multi-reader deployments).