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:
| 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) |
| macOS | Hexadecimal USB location ID | 0x14100000 (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
- C++
- C#
- Python
Namespace: Rik | Header: #include "Reader/ReaderDiscovery.h"
#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;
}
Namespace: rfIDEAS.ReaderIntegrationKit
using rfIDEAS.ReaderIntegrationKit;
using rfIDEAS.ReaderIntegrationKit.Objects;
var readers = ReaderDiscovery.DiscoverUsbReaders();
Console.WriteLine($"Found {readers.Count} reader(s):\n");
foreach (var reader in readers)
{
Console.WriteLine($" VID:PID: 0x{reader.DeviceId.VendorId:X4}:0x{reader.DeviceId.ProductId:X4}");
Console.WriteLine($" Path: {reader.DeviceId.UsbPathString}");
Console.WriteLine($" Serial: {reader.DeviceId.SerialNumberString}");
Console.WriteLine();
}
Module: reader_integration_kit.facade
from reader_integration_kit.facade import ReaderDiscovery
readers = ReaderDiscovery.discover_usb_readers()
print(f"Found {len(readers)} reader(s):\n")
for reader in readers:
print(f" VID:PID: 0x{reader.DeviceId.VendorId:04X}:0x{reader.DeviceId.ProductId:04X}")
print(f" Path: {reader.DeviceId.UsbPath.decode()}")
print(f" Serial: {reader.DeviceId.SerialNumber.decode()}")
print()
Discover Then Connect
The returned ReaderDefinition objects can be passed directly to the reader constructor -- no manual field copying is required:
- C++
- C#
- Python
#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;
}
using rfIDEAS.ReaderIntegrationKit;
using rfIDEAS.ReaderIntegrationKit.Objects;
using rfIDEAS.ReaderIntegrationKit.Exceptions;
var readers = ReaderDiscovery.DiscoverUsbReaders();
if (readers.Count == 0)
{
Console.WriteLine("No readers found.");
return;
}
Console.WriteLine($"Discovered {readers.Count} reader(s)\n");
foreach (var readerDef in readers)
{
Console.WriteLine($"Connecting to reader at {readerDef.DeviceId.UsbPathString}...");
try
{
using var reader = new Reader(readerDef);
reader.Init();
var metadata = reader.GetMetadata();
Console.WriteLine($" Part Number: {metadata.PartNumber}");
Console.WriteLine($" Serial: {metadata.ESN}\n");
}
catch (ReaderException ex)
{
Console.WriteLine($" Error: {ex.Message}\n");
}
}
from reader_integration_kit.facade import ReaderDiscovery, Reader
from reader_integration_kit.errors import ReaderException
readers = ReaderDiscovery.discover_usb_readers()
if not readers:
print("No readers found.")
exit()
print(f"Discovered {len(readers)} reader(s)\n")
for reader_def in readers:
path = reader_def.DeviceId.UsbPath.decode()
print(f"Connecting to reader at {path}...")
try:
with Reader(reader_def) as reader:
reader.init()
metadata = reader.get_metadata()
print(f" Part Number: {metadata.get('PartNumber')}")
print(f" Serial: {metadata.get('ESN')}\n")
except ReaderException as e:
print(f" Error: {e.message}\n")
Discovered ReaderDefinition Fields
Each returned ReaderDefinition has the following fields populated:
| Field | Type | Description |
|---|---|---|
DeviceId.VendorId | uint16_t | USB Vendor ID |
DeviceId.ProductId | uint16_t | USB Product ID |
DeviceId.UsbPath | char[512] | Topological USB path (stable per physical port) |
DeviceId.SerialNumber | char[256] | USB serial number from the device descriptor |
ProtocolType | ProtocolType | The 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:
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.
#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);
| Parameter | Type | Description |
|---|---|---|
rikResult | RikResult& | Error output |
readers | ReaderDefinition* | Output buffer, or nullptr to query count only |
readerCount | size_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).
#!/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:
# 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
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:
- Open System Information (Apple menu > About This Mac > System Report, or run
system_profiler SPUSBDataTypein Terminal). - Select USB from the Hardware sidebar.
- Locate your reader in the USB device tree.
- 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.
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).