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