Skip to main content

Credential Callbacks

Event-driven credential detection using OnCredentialPresented. Instead of polling with GetCardData, register a callback that fires automatically when a card is presented to the reader.

Single Reader, Single Callback

The simplest case -- one reader, one callback function.

#include <iostream>
#include <thread>
#include <chrono>
#include "Reader/Reader.h"

int main() {
using namespace Rik;
using namespace RikCommon;

ReaderHandle handle = nullptr;

try {
ReaderDefinition readerDef{};
readerDef.DeviceId.VendorId = 0x0C27;
readerDef.DeviceId.ProductId = 0x3BFA;
readerDef.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;

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

// Register a credential callback
auto subId = reader->OnCredentialPresented([](CardData& card) {
std::cout << "Card detected (" << card.GetBitCount()
<< " bits): " << card.AsString() << std::endl;
});

// Main thread keeps running while callbacks fire in the background
std::cout << "Listening for cards... Press Ctrl+C to exit." << std::endl;
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}

// Cleanup (unreachable in this example, but shown for completeness)
reader->UnsubscribeCredentialCallback(subId);
reader->Close();
AbstractReader::DestroyInstance(handle);
return 0;

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

Multiple Callbacks on One Reader

Register several callbacks on the same reader. Each callback receives its own subscription ID and fires independently. This is useful when different parts of your application need to react to the same card event -- for example, one callback logs the event while another updates a UI.

#include <iostream>
#include <thread>
#include <chrono>
#include "Reader/Reader.h"

int main() {
using namespace Rik;
using namespace RikCommon;

ReaderHandle handle = nullptr;

try {
ReaderDefinition readerDef{};
readerDef.DeviceId.VendorId = 0x0C27;
readerDef.DeviceId.ProductId = 0x3BFA;
readerDef.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;

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

// Callback 1: log the raw data
auto logSub = reader->OnCredentialPresented([](CardData& card) {
std::cout << "[LOG] " << card.GetBitCount()
<< " bits: " << card.AsString() << std::endl;
});

// Callback 2: count total scans
int scanCount = 0;
auto countSub = reader->OnCredentialPresented([&scanCount](CardData& card) {
scanCount++;
std::cout << "[COUNT] Total scans: " << scanCount << std::endl;
});

// Callback 3: alert on high-bit-count cards
auto alertSub = reader->OnCredentialPresented([](CardData& card) {
if (card.GetBitCount() > 48) {
std::cout << "[ALERT] High bit count card: "
<< card.GetBitCount() << std::endl;
}
});

std::cout << "Listening with 3 callbacks..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(60));

// Unsubscribe all
reader->UnsubscribeCredentialCallback(logSub);
reader->UnsubscribeCredentialCallback(countSub);
reader->UnsubscribeCredentialCallback(alertSub);

reader->Close();
AbstractReader::DestroyInstance(handle);
return 0;

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

Multiple Readers with Callbacks

Each reader instance has its own independent set of callbacks. This example connects two readers and registers a callback on each. The reader's metadata is captured in the callback closure so you can tell which reader detected the card.

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include "Reader/Reader.h"

int main() {
using namespace Rik;
using namespace RikCommon;

ReaderHandle handle1 = nullptr;
ReaderHandle handle2 = nullptr;

try {
// Reader 1
ReaderDefinition readerDef1{};
readerDef1.DeviceId.VendorId = 0x0C27;
readerDef1.DeviceId.ProductId = 0x3BFA;
readerDef1.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;

handle1 = AbstractReader::CreateReaderInstance(readerDef1, 3);
auto* reader1 = dynamic_cast<Reader*>(
AbstractReader::GetInstance(handle1)
);
reader1->Init();
std::string name1 = reader1->GetMetadataStruct().PartNumber;

// Reader 2
ReaderDefinition readerDef2{};
readerDef2.DeviceId.VendorId = 0x0C27;
readerDef2.DeviceId.ProductId = 0x3BFB;
readerDef2.ProtocolType = PROTOCOL_TYPE_FEATURE_REPORT;

handle2 = AbstractReader::CreateReaderInstance(readerDef2, 3);
auto* reader2 = dynamic_cast<Reader*>(
AbstractReader::GetInstance(handle2)
);
reader2->Init();
std::string name2 = reader2->GetMetadataStruct().PartNumber;

// Register callbacks -- capture the reader name for identification
auto sub1 = reader1->OnCredentialPresented(
[name1](CardData& card) {
std::cout << "[" << name1 << "] Card: "
<< card.AsString() << std::endl;
});

auto sub2 = reader2->OnCredentialPresented(
[name2](CardData& card) {
std::cout << "[" << name2 << "] Card: "
<< card.AsString() << std::endl;
});

std::cout << "Listening on " << name1 << " and "
<< name2 << "..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(60));

// Cleanup
reader1->UnsubscribeCredentialCallback(sub1);
reader2->UnsubscribeCredentialCallback(sub2);

reader1->Close();
AbstractReader::DestroyInstance(handle1);
reader2->Close();
AbstractReader::DestroyInstance(handle2);
return 0;

} catch (const ReaderException& e) {
std::cerr << "Error: " << e.Message << std::endl;
if (handle1) AbstractReader::DestroyInstance(handle1);
if (handle2) AbstractReader::DestroyInstance(handle2);
return 1;
}
}

Dynamic Subscribe / Unsubscribe

Callbacks can be added and removed at any time during the reader's lifetime. This is useful for temporarily enabling credential detection -- for example, only listening during a specific workflow step.

auto* reader = dynamic_cast<Reader*>(
AbstractReader::GetInstance(handle)
);

// Start listening
auto subId = reader->OnCredentialPresented([](CardData& card) {
std::cout << "Card: " << card.AsString() << std::endl;
});

// ... perform some workflow that needs credential detection ...

// Stop listening -- the reader is still connected, but the callback
// no longer fires
reader->UnsubscribeCredentialCallback(subId);

// ... do other work with the reader (configure, beep, etc.) ...

// Start listening again with a different callback
auto subId2 = reader->OnCredentialPresented([](CardData& card) {
std::cout << "Phase 2 card: " << card.AsString() << std::endl;
});

Thread Safety Considerations

Credential callbacks fire on a library-managed background thread (one per reader). All callbacks registered on the same reader are invoked sequentially -- they will not run in parallel with each other.

However, if your callback accesses state that is also used by another thread (for example, updating a collection that the main thread reads), you need to synchronize that access yourself:

#include <iostream>
#include <mutex>
#include <vector>
#include "Reader/Reader.h"

std::mutex dataMutex;
std::vector<std::string> cardLog;

auto subId = reader->OnCredentialPresented([](CardData& card) {
std::lock_guard<std::mutex> lock(dataMutex);
cardLog.push_back(card.AsString());
std::cout << "Logged card #" << cardLog.size() << std::endl;
});

See Also