Simulate a Card Read
Overview
Card read simulation allows you to test access control logic without physical hardware. This is invaluable for validating card formats, testing access level configurations, verifying event generation, and debugging integration issues. The simulation sends a virtual card read event to a reader, causing the system to process it exactly as if a physical card was presented.
Key Concepts
| Concept |
Description |
| SimulateCardRead |
Command that sends virtual card data to a reader |
| Facility Code |
Site identifier encoded on the card |
| Encoded Card Number |
Full encoded number from the card data |
| Card Format |
Format definition used to interpret the card data |
| Event Publishing |
Mechanism for sending commands to the system |
Use Cases
| Scenario |
Description |
| Format Testing |
Verify card format configuration before issuing physical cards |
| Access Level Verification |
Test that access levels grant/deny correctly |
| Integration Testing |
Validate that event subscribers receive expected data |
| Demonstration |
Show system functionality without needing physical cards |
| Troubleshooting |
Isolate whether issues are hardware or configuration related |
Prerequisites
Before simulating card reads, ensure you have:
- ✅ A configured reader on an online controller
- ✅ A card format that matches the simulated card data
- ✅ (Optional) A person with a card assigned using the same card number
C# Examples
Basic Card Read Simulation
using Feenics.Keep.WebApi.Wrapper;
using Feenics.Keep.WebApi.Model;
// Get required objects
var reader = await client.GetByHrefAsync<MercuryReaderInfo>(readerHref);
var cardFormat = await client.GetByHrefAsync<CardFormatInfo>(cardFormatHref);
var controller = await client.GetByHrefAsync<MercuryControllerInfo>(controllerHref);
// Simulate a card read
await client.PublishEventAsync(
currentInstance,
"MercuryCommands",
new MonikerItem
{
Namespace = "MercuryServiceCommands",
Nickname = "mercury:command-simulateCardRead"
},
DateTime.UtcNow,
new
{
Reason = "Testing card format configuration",
FacilityCode = 45,
EncodedCardNumber = 12345
},
reader.AsObjectLink("Reader"),
cardFormat.AsObjectLink("CardFormat"),
controller.AsObjectLink("Controller")
);
Console.WriteLine($"Simulated card read: FC={45}, Card={12345} at {reader.CommonName}");
Simulate Read for an Existing Person’s Card
// Find a person with an assigned card
var people = await client.SearchAsync<PersonInfo>(
currentInstance,
"{\"_t\": \"Person\", \"CommonName\":\"Smith, John\"}",
0,
1
);
var person = people.FirstOrDefault();
if (person?.CardAssignments != null && person.CardAssignments.Any())
{
// Get the first active card assignment
var card = person.CardAssignments
.FirstOrDefault(c => !c.IsDisabled);
if (card != null)
{
// Get the card format to obtain facility code
var cardFormat = await client.GetByHrefAsync<CardFormatInfo>(cardFormatHref);
await client.PublishEventAsync(
currentInstance,
"MercuryCommands",
new MonikerItem
{
Namespace = "MercuryServiceCommands",
Nickname = "mercury:command-simulateCardRead"
},
DateTime.UtcNow,
new
{
Reason = $"Testing access for {person.CommonName}",
FacilityCode = cardFormat.FacilityCode ?? 0,
EncodedCardNumber = card.EncodedCardNumber
},
reader.AsObjectLink("Reader"),
cardFormat.AsObjectLink("CardFormat"),
controller.AsObjectLink("Controller")
);
Console.WriteLine($"Simulated card read for {person.CommonName}");
}
}
Batch Test Multiple Cards
/// <summary>
/// Simulates card reads for a list of card numbers to test access levels
/// </summary>
public async Task TestCardAccessAsync(
MercuryReaderInfo reader,
CardFormatInfo cardFormat,
MercuryControllerInfo controller,
int facilityCode,
IEnumerable<int> cardNumbers,
int delayMs = 2000)
{
foreach (var cardNumber in cardNumbers)
{
Console.WriteLine($"Simulating card {cardNumber}...");
await client.PublishEventAsync(
currentInstance,
"MercuryCommands",
new MonikerItem
{
Namespace = "MercuryServiceCommands",
Nickname = "mercury:command-simulateCardRead"
},
DateTime.UtcNow,
new
{
Reason = "Batch access level testing",
FacilityCode = facilityCode,
EncodedCardNumber = cardNumber
},
reader.AsObjectLink("Reader"),
cardFormat.AsObjectLink("CardFormat"),
controller.AsObjectLink("Controller")
);
// Wait between reads to observe individual events
await Task.Delay(delayMs);
}
Console.WriteLine($"Completed simulation of {cardNumbers.Count()} cards");
}
// Usage
await TestCardAccessAsync(
reader,
cardFormat,
controller,
facilityCode: 45,
cardNumbers: new[] { 1001, 1002, 1003, 1004, 1005 },
delayMs: 3000
);
Complete Simulation with Event Monitoring
/// <summary>
/// Simulates a card read and waits for the resulting event
/// </summary>
public async Task<EventInfo?> SimulateAndWaitForEventAsync(
MercuryReaderInfo reader,
CardFormatInfo cardFormat,
MercuryControllerInfo controller,
int facilityCode,
int cardNumber,
TimeSpan timeout)
{
var startTime = DateTime.UtcNow;
// Simulate the card read
await client.PublishEventAsync(
currentInstance,
"MercuryCommands",
new MonikerItem
{
Namespace = "MercuryServiceCommands",
Nickname = "mercury:command-simulateCardRead"
},
DateTime.UtcNow,
new
{
Reason = "Simulation with event monitoring",
FacilityCode = facilityCode,
EncodedCardNumber = cardNumber
},
reader.AsObjectLink("Reader"),
cardFormat.AsObjectLink("CardFormat"),
controller.AsObjectLink("Controller")
);
// Poll for the resulting event
var deadline = DateTime.UtcNow.Add(timeout);
while (DateTime.UtcNow < deadline)
{
await Task.Delay(500);
// Query recent events for this reader
var events = await client.GetEventsAsync(
currentInstance,
fromDate: startTime,
forKeys: new[] { reader.Key });
var accessEvent = events.FirstOrDefault(e =>
e.MessageLong?.Contains(cardNumber.ToString()) == true);
if (accessEvent != null)
{
Console.WriteLine($"Event received: {accessEvent.MessageLong}");
return accessEvent;
}
}
Console.WriteLine("Timeout waiting for event");
return null;
}
cURL Examples
Basic Card Read Simulation
ACCESS_TOKEN="your-access-token"
INSTANCE_KEY="your-instance-key"
READER_KEY="your-reader-key"
CARDFORMAT_KEY="your-cardformat-key"
CONTROLLER_KEY="your-controller-key"
curl -X POST \
"https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/eventmessagesink" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"$type": "Feenics.Keep.WebApi.Model.EventMessagePosting, Feenics.Keep.WebApi.Model",
"OccurredOn": "'"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"'",
"AppKey": "MercuryCommands",
"EventTypeMoniker": {
"$type": "Feenics.Keep.WebApi.Model.MonikerItem, Feenics.Keep.WebApi.Model",
"Namespace": "MercuryServiceCommands",
"Nickname": "mercury:command-simulateCardRead"
},
"RelatedObjects": [
{
"$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
"LinkedObjectKey": "'"${READER_KEY}"'",
"Relation": "Reader"
},
{
"$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
"LinkedObjectKey": "'"${CARDFORMAT_KEY}"'",
"Relation": "CardFormat"
},
{
"$type": "Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model",
"LinkedObjectKey": "'"${CONTROLLER_KEY}"'",
"Relation": "Controller"
}
],
"EventData": {
"Reason": "Testing via cURL",
"FacilityCode": 45,
"EncodedCardNumber": 12345
}
}'
Simulate with Specific Card Number
# Set your card data
FACILITY_CODE=45
CARD_NUMBER=67890
curl -X POST \
"https://api.us.acresecurity.cloud/api/f/${INSTANCE_KEY}/eventmessagesink" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"\$type\": \"Feenics.Keep.WebApi.Model.EventMessagePosting, Feenics.Keep.WebApi.Model\",
\"OccurredOn\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",
\"AppKey\": \"MercuryCommands\",
\"EventTypeMoniker\": {
\"\$type\": \"Feenics.Keep.WebApi.Model.MonikerItem, Feenics.Keep.WebApi.Model\",
\"Namespace\": \"MercuryServiceCommands\",
\"Nickname\": \"mercury:command-simulateCardRead\"
},
\"RelatedObjects\": [
{
\"\$type\": \"Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model\",
\"LinkedObjectKey\": \"${READER_KEY}\",
\"Relation\": \"Reader\"
},
{
\"\$type\": \"Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model\",
\"LinkedObjectKey\": \"${CARDFORMAT_KEY}\",
\"Relation\": \"CardFormat\"
},
{
\"\$type\": \"Feenics.Keep.WebApi.Model.ObjectLinkItem, Feenics.Keep.WebApi.Model\",
\"LinkedObjectKey\": \"${CONTROLLER_KEY}\",
\"Relation\": \"Controller\"
}
],
\"EventData\": {
\"Reason\": \"Testing specific card\",
\"FacilityCode\": ${FACILITY_CODE},
\"EncodedCardNumber\": ${CARD_NUMBER}
}
}"
Event Data Parameters
| Parameter |
Type |
Required |
Description |
Reason |
string |
No |
Description of why simulation is being performed |
FacilityCode |
int |
Yes |
Facility code to simulate |
EncodedCardNumber |
int |
Yes |
Card number to simulate |
Expected Results
After simulating a card read, you should see one of the following events:
| Event |
Description |
| Access Granted |
Card is valid and person has access to the reader |
| Access Denied - Card not found |
Card is configured in the panel. May be expired or not configured on the panel. |
| Access Denied - Door Schedule |
Person is not allowed access at this time |
Best Practices
| Practice |
Description |
| Use descriptive reasons |
Document why simulation was performed |
| Test all card formats |
Verify each format works before production |
| Test edge cases |
Try expired cards, unknown cards, blocked persons |
| Monitor events |
Subscribe to events to see simulation results |
| Use in staging first |
Test configuration before deploying to production |
| Clear test data |
Remove test persons/cards after validation |
Troubleshooting
| Issue |
Possible Cause |
Solution |
| No event generated |
Controller offline |
Verify controller status |
| Unknown card error |
Card format mismatch |
Check facility code and bit positions, try DB Push |
| Wrong person identified |
Duplicate card number |
Verify unique card assignments |
| Access denied unexpectedly |
Access level not linked |
Check person’s access level assignments |
| Simulation timeout |
Network latency |
Increase timeout or check connectivity |
| Invalid card format |
Format not on controller |
Assign card format to controller |