Skip to main content

Discovering USB Readers

RIK provides a built-in reader discovery API that scans the USB bus for connected rf IDEAS readers and returns ready-to-use ReaderDefinition objects. This is the recommended approach when you need to:

  • Find all connected readers before deciding which to open
  • Handle environments with multiple readers (even identical models)
  • Avoid hardcoding VID/PID values

Each discovered reader includes its VID/PID, topological USB path, serial number, and protocol type -- everything needed to connect.

For environments where the RIK SDK is not available (scripts, quick diagnostics), platform-specific alternatives are described below for Linux, Windows, and macOS.

Path Formats

Each discovered reader includes a topological USB path that identifies the physical port:

PlatformFormatExample
LinuxB-P or B-P.P.P (bus-port)1-7, 1-7.2
WindowsLocation path stringPCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7)
macOSHexadecimal USB location ID0x14100000 (decimal also accepted)

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.


ReaderDiscovery

The ReaderDiscovery class provides a static method that discovers all connected rf IDEAS USB readers and returns ready-to-use ReaderDefinition objects.

Discover All Connected Readers

Namespace: Rik | Header: #include "Reader/ReaderDiscovery.h"

discover_readers.cpp
#include <iostream>
#include <iomanip>
#include "Reader/ReaderDiscovery.h"

int main() {
auto readers = Rik::ReaderDiscovery::DiscoverUsbReaders();

std::cout << "Found " << readers.size() << " reader(s):\n\n";

for (const auto& reader : readers) {
std::cout << " VID:PID: " << std::hex << std::setfill('0')
<< "0x" << std::setw(4) << reader.DeviceId.VendorId << ":"
<< "0x" << std::setw(4) << reader.DeviceId.ProductId << std::dec << "\n"
<< " Path: " << reader.DeviceId.UsbPath << "\n"
<< " Serial: " << reader.DeviceId.SerialNumber << "\n\n";
}

return 0;
}

Discover Then Connect

The returned ReaderDefinition objects can be passed directly to the reader constructor -- no manual field copying is required:

discover_and_connect.cpp
#include <iostream>
#include "Reader/ReaderDiscovery.h"
#include "Reader/AbstractReader.h"
#include "Reader/Reader.h"

int main() {
using namespace Rik;

// 1. Discover all connected rf IDEAS USB readers
auto readers = ReaderDiscovery::DiscoverUsbReaders();

if (readers.empty()) {
std::cerr << "No readers found.\n";
return 1;
}

std::cout << "Discovered " << readers.size() << " reader(s)\n\n";

// 2. Connect to each discovered reader
for (const auto& readerDef : readers) {
std::cout << "Connecting to reader at " << readerDef.DeviceId.UsbPath << "...\n";

ReaderHandle handle = nullptr;
try {
handle = AbstractReader::CreateReaderInstance(readerDef, 3);
auto* reader = dynamic_cast<Reader*>(
AbstractReader::GetInstance(handle));
reader->Init();

auto metadata = reader->GetMetadataStruct();
std::cout << " Part Number: " << metadata.PartNumber << "\n";
std::cout << " Serial: " << metadata.ESN << "\n\n";

AbstractReader::DestroyInstance(handle);

} catch (const ReaderException& e) {
std::cerr << " Error: " << e.Message << "\n\n";
if (handle) AbstractReader::DestroyInstance(handle);
}
}

return 0;
}

Discovered ReaderDefinition Fields

Each returned ReaderDefinition has the following fields populated:

FieldTypeDescription
DeviceId.VendorIduint16_tUSB Vendor ID
DeviceId.ProductIduint16_tUSB Product ID
DeviceId.UsbPathchar[512]Topological USB path (stable per physical port)
DeviceId.SerialNumberchar[256]USB serial number from the device descriptor
ProtocolTypeProtocolTypeThe communication protocol for this reader, as detected during discovery. Do not modify this field on a discovered ReaderDefinition.

CMake Setup (C++ only)

Link to the Application library -- ReaderDiscovery is included:

CMakeLists.txt
find_package(ReaderIntegrationKit REQUIRED)

add_executable(discover_readers discover_readers.cpp)
target_link_libraries(discover_readers
PRIVATE ReaderIntegrationKit::ReaderIntegrationKit)

C API -- Rik_DiscoverUsbReaders

Reader discovery is also available through the C API, making it accessible from C and other FFI-capable languages.

#include "Reader/Reader_C.h"

Two-Call Pattern

Rik_DiscoverUsbReaders uses a two-call pattern: call first with readers set to nullptr to get the count, then allocate a buffer and call again to retrieve the results.

discover_readers_c.cpp
#include <stdio.h>
#include <stdlib.h>
#include "Reader/Reader_C.h"

int main() {
RikResult result = {0};

// First call: get the number of readers
size_t readerCount = 0;
Rik_DiscoverUsbReaders(result, nullptr, &readerCount);

if (result.HasException) {
fprintf(stderr, "Discovery failed: %s\n", result.Message);
return 1;
}

if (readerCount == 0) {
printf("No readers found.\n");
return 0;
}

// Allocate buffer
RikCommon::ReaderDefinition* readers =
(RikCommon::ReaderDefinition*)calloc(readerCount, sizeof(RikCommon::ReaderDefinition));

// Second call: retrieve reader definitions
Rik_DiscoverUsbReaders(result, readers, &readerCount);

if (result.HasException) {
fprintf(stderr, "Discovery failed: %s\n", result.Message);
free(readers);
return 1;
}

printf("Found %zu reader(s):\n\n", readerCount);

for (size_t i = 0; i < readerCount; i++) {
printf(" VID:PID: %04X:%04X\n", readers[i].DeviceId.VendorId,
readers[i].DeviceId.ProductId);
printf(" Path: %s\n", readers[i].DeviceId.UsbPath);
printf(" Serial: %s\n\n", readers[i].DeviceId.SerialNumber);

// Each reader can be opened directly:
// ReaderPtr handle = RikReader_Open(result, readers[i], 3);
}

free(readers);
return 0;
}

Function Signature

void Rik_DiscoverUsbReaders(
RikResult& rikResult,
RikCommon::ReaderDefinition* readers,
size_t* readerCount);
ParameterTypeDescription
rikResultRikResult&Error output
readersReaderDefinition*Output buffer, or nullptr to query count only
readerCountsize_t*In/out: buffer size on input, number of readers found on output

See C API Reference for full details.


Linux -- Shell Script

The following shell script enumerates USB devices on Linux and displays their stable topological paths by reading from sysfs. Save it as enum_usb_linux.sh and make it executable (chmod +x enum_usb_linux.sh).

enum_usb_linux.sh
#!/bin/bash
#
# enum_usb_linux.sh - Enumerate USB devices and show topological paths
#
# This utility helps discover the stable topological USB path (busnum-devpath)
# for devices matching known VID/PID combinations. The topological path remains
# stable across device reconnections to the same physical port.
#
# Usage:
# ./enum_usb_linux.sh # Show all known devices
# ./enum_usb_linux.sh --all # Show all USB devices
# ./enum_usb_linux.sh 0c27:3bfa # Search for specific VID:PID
#

# Known VID/PID combinations for supported readers
declare -A KNOWN_DEVICES=(
["0c27:0001"]="RF IDeas Bootloader"
["0c27:0105"]="RF IDeas SafeCom"
["0c27:232a"]="RF IDeas CDC"
["0c27:3a01"]="RF IDeas KPIT"
["0c27:3b1e"]="RF IDeas BLE"
["0c27:3b4c"]="RFIDeas Legic"
["0c27:3b57"]="RF IDeas Wiegand"
["0c27:3b80"]="RFIDeas pcProx"
["0c27:3b81"]="RFIDeas pcProx USB"
["0c27:3bfa"]="RFIDeas pcProx Plus"
["0c27:3bfb"]="RF IDeas ProxAdv"
["0c27:3bfc"]="RFIDeas pcProx Plus (Legic)"
["0c27:ccda"]="RF IDeas CCID"
["0c27:ccdb"]="FIDO CCID"
)

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

print_header() {
printf "\n${CYAN}%-12s %-10s %-40s %s${NC}\n" \
"TOPO PATH" "VID:PID" "DEVICE NAME" "HIDRAW"
printf "%s\n" \
"--------------------------------------------------------------------------------"
}

get_hidraw_for_device() {
local bus=$1
local devpath=$2
local hidraw_list=""

# Search through hidraw devices to find ones matching this USB device
for hidraw in /sys/class/hidraw/hidraw*; do
[ -e "$hidraw" ] || continue

# Resolve the device path and walk up to find USB device
local resolved=$(realpath "$hidraw/device" 2>/dev/null)
[ -z "$resolved" ] && continue

local current="$resolved"
while [ -n "$current" ] && [ "$current" != "/" ]; do
if [ -f "$current/busnum" ] && [ -f "$current/devpath" ]; then
local hid_bus=$(cat "$current/busnum" 2>/dev/null)
local hid_devpath=$(cat "$current/devpath" 2>/dev/null)

if [ "$hid_bus" = "$bus" ] && [ "$hid_devpath" = "$devpath" ]; then
local name=$(basename "$hidraw")
if [ -z "$hidraw_list" ]; then
hidraw_list="/dev/$name"
else
hidraw_list="$hidraw_list, /dev/$name"
fi
fi
break
fi
current=$(dirname "$current")
done
done

echo "$hidraw_list"
}

enumerate_devices() {
local filter_vidpid="$1"
local show_all="$2"

print_header

local found=0

# Iterate through USB devices in sysfs
for device in /sys/bus/usb/devices/*; do
# Skip interfaces (contain colons like 1-7:1.0)
[[ $(basename "$device") == *:* ]] && continue

# Skip root hubs (usb1, usb2, etc.)
[[ $(basename "$device") == usb* ]] && continue

# Check if this is a USB device with required attributes
[ -f "$device/idVendor" ] || continue
[ -f "$device/idProduct" ] || continue
[ -f "$device/busnum" ] || continue
[ -f "$device/devpath" ] || continue

local vid=$(cat "$device/idVendor" 2>/dev/null)
local pid=$(cat "$device/idProduct" 2>/dev/null)
local busnum=$(cat "$device/busnum" 2>/dev/null)
local devpath=$(cat "$device/devpath" 2>/dev/null)
local manufacturer=$(cat "$device/manufacturer" 2>/dev/null)
local product=$(cat "$device/product" 2>/dev/null)

local vidpid="${vid}:${pid}"
local topo_path="${busnum}-${devpath}"

# Build device name
local device_name=""
if [ -n "$manufacturer" ] && [ -n "$product" ]; then
device_name="$manufacturer $product"
elif [ -n "$product" ]; then
device_name="$product"
elif [ -n "$manufacturer" ]; then
device_name="$manufacturer"
fi

# Check if this is a known device
local known_name="${KNOWN_DEVICES[$vidpid]}"
if [ -n "$known_name" ]; then
device_name="$known_name"
fi

# Apply filters
if [ -n "$filter_vidpid" ]; then
# Filter by specific VID:PID
[[ "$vidpid" != "$filter_vidpid" ]] && continue
elif [ "$show_all" != "true" ]; then
# Only show known devices
[ -z "$known_name" ] && continue
fi

# Get associated hidraw devices
local hidraw=$(get_hidraw_for_device "$busnum" "$devpath")

# Print the device info
if [ -n "$known_name" ]; then
printf "${GREEN}%-12s${NC} %-10s %-40s %s\n" \
"$topo_path" "$vidpid" "$device_name" "$hidraw"
else
printf "%-12s %-10s %-40s %s\n" \
"$topo_path" "$vidpid" "$device_name" "$hidraw"
fi

found=$((found + 1))
done

if [ $found -eq 0 ]; then
if [ -n "$filter_vidpid" ]; then
printf "${YELLOW}No devices found matching VID:PID %s${NC}\n" \
"$filter_vidpid"
elif [ "$show_all" != "true" ]; then
printf "${YELLOW}No known devices found. " \
"Use --all to show all USB devices.${NC}\n"
else
printf "${YELLOW}No USB devices found.${NC}\n"
fi
fi

printf "\n"
}

show_usage() {
echo "Usage: $0 [OPTIONS] [VID:PID]"
echo ""
echo "Enumerate USB devices and show their stable topological paths."
echo ""
echo "Options:"
echo " --all Show all USB devices, not just known ones"
echo " --help Show this help message"
echo ""
echo "Arguments:"
echo " VID:PID Filter by specific vendor:product ID (e.g., 0c27:3bfa)"
echo ""
echo "Known devices:"
for vidpid in $(echo "${!KNOWN_DEVICES[@]}" | tr ' ' '\n' | sort); do
printf " %-12s %s\n" "$vidpid" "${KNOWN_DEVICES[$vidpid]}"
done
echo ""
echo "The TOPO PATH can be used as the DeviceId.UsbPath value."
}

# Main
show_all=false
filter_vidpid=""

for arg in "$@"; do
case "$arg" in
--all)
show_all=true
;;
--help|-h)
show_usage
exit 0
;;
*:*)
filter_vidpid="$arg"
;;
*)
echo "Unknown argument: $arg"
show_usage
exit 1
;;
esac
done

enumerate_devices "$filter_vidpid" "$show_all"

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 pcProx Plus /dev/hidraw1
1-8 0c27:3bfa RFIDeas pcProx Plus /dev/hidraw2

The TOPO PATH column is the value that ReaderDiscovery populates into 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:

enum_usb_windows.ps1
# 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:

  1. Open Device Manager
  2. Find your reader under Human Interface Devices or Universal Serial Bus devices
  3. Right-click > Properties > Details tab
  4. Select Location paths from the property dropdown
  5. The value (e.g., PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7)) is the USB path to use

macOS -- ioreg

On macOS, use the ioreg command-line utility or System Information.app to discover USB device location IDs. The location ID is a 32-bit hexadecimal value that identifies the physical USB port. Pass this value as DeviceId.UsbPath in your ReaderDefinition. Both hexadecimal (e.g., 0x14100000) and decimal formats are accepted.

Command line (run in Terminal):

ioreg -p IOUSB -l -w 0 | grep -E "(idVendor|idProduct|locationID|USB Product Name)"

Look for your rf IDEAS reader by vendor ID (0x0c27) and note the locationID value.

System Information.app:

  1. Open System Information (Apple menu > About This Mac > System Report, or run system_profiler SPUSBDataType in Terminal).
  2. Select USB from the Hardware sidebar.
  3. Locate your reader in the USB device tree.
  4. Note the Location ID value (displayed as a hexadecimal string, e.g., 0x14100000).

The Location ID is the value to use as DeviceId.UsbPath.


Using a Manually Discovered Path

If you obtained a USB path through shell scripts, PowerShell, Device Manager, ioreg, or System Information (rather than through ReaderDiscovery), you can set it directly on a ReaderDefinition:

ReaderDefinition readerDef{};
readerDef.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;

// Linux path from enum_usb_linux.sh
std::strncpy(readerDef.DeviceId.UsbPath, "1-7",
sizeof(readerDef.DeviceId.UsbPath) - 1);

// Windows path from PowerShell or Device Manager
// std::strncpy(readerDef.DeviceId.UsbPath,
// "PCIROOT(0)#PCI(1400)#USBROOT(0)#USB(7)",
// sizeof(readerDef.DeviceId.UsbPath) - 1);

// macOS path from ioreg or System Information
// std::strncpy(readerDef.DeviceId.UsbPath, "0x14100000",
// sizeof(readerDef.DeviceId.UsbPath) - 1);

See Connect by USB Path for full examples in all languages.

tip

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).