Skip to content

Events

An event is a message that reports something that already happened on a device.


What is an Event?

+------------------+      +------------------+      +------------------+
|  Physical World  | ---> |     Adapter      | ---> |    PQ System     |
+------------------+      +------------------+      +------------------+
   "Door opened"            Detects change           Receives event
   (something happened)     Creates EventMessage     Logs, notifies,
                            Publishes to NATS        triggers rules

Events are:

  • Past tense - they report what already happened
  • Immutable - you cannot change or undo an event
  • One-way - adapter sends, PQ receives (no response expected)
  • Historical - they become part of audit trail

Event Processing Approaches

Modern adapters can handle events using two approaches:

Pattern 2: Manual Translation

Traditional approach where you manually route events in Protocol.OnEventReceived():

protected override async Task OnEventReceived(DeviceEvent evt)
{
    var door = FindByAddress(evt.DoorId) as DoorThing;
    if (door is null) return;

    switch (evt.Code)
    {
        case 0x1000: // Access Granted
            await door.PublishAccessGranted(...);
            break;
        // ... more cases
    }
}

Best for:

  • Simple devices with 5-10 event types
  • Flat addressing structure
  • Single Thing type handles all events

See Pattern 2: Event Translation for details.

EventRouter: Declarative Routing

Declarative approach using routing rules for automatic event dispatch:

public void Configure(IEventRoute<DeviceEvent> router)
{
    router
        .When(e => e.Code == 0x1000)
        .WithAddress<DoorThing, uint>(e => e.DoorId)
        .WithPerson(e => e.PersonId)
        .WithCredential(e => e.CredentialId)
        .Handle(async (door, personId, credentialId, evt) =>
        {
            await door.PublishAccessGranted(...);
        });
}

Best for:

  • Complex devices with 20+ event types
  • Hierarchical addressing (Panel → SIO → Point)
  • Multiple Thing types handling different events
  • Identity resolution requirements

See Event Routing for details.


Classifying Device Messages

Not all messages from devices are events. Before processing, classify each message type:

Message Type Purpose Process as Event?
Audit Event Something happened (access granted, door forced) YES - ALL
Status Update Current state (door is open, device online) NO - publish as status
Control Message Protocol communication (ACK, keepalive) NO - handle internally

Critical: Process ALL audit events. Never silently drop events you don't recognize - publish them using the thing.Unknown() extension method. For EventRouter-based adapters, register a router.Unhandled() handler.

See Event Types for detailed classification rules.


Event vs Status vs Command

I want to... Use
Report something that happened Event
Report current state Status
Request an action Command
EVENT:   "Access was granted to John at 10:15:03"
         (happened, recorded, done)

STATUS:  "Door is unlocked"
         (current state, may change any moment)

COMMAND: "Unlock the door"
         (request, not yet executed)

Common Event Types

Access Control Events

Event Meaning
access.granted Someone successfully gained access
access.denied Access attempt was rejected
access.unknown Credential not recognized

Door Events

Event Meaning
door.opened Door physically opened
door.closed Door physically closed
door.forced Door opened without valid access
door.held Door held open too long

Device Events

Event Meaning
device.online Device connected
device.offline Device disconnected
device.tamper Tamper switch triggered
device.error Device reported error

Fire Events

Event Meaning
fire.alarm Fire alarm active
fire.alarm.general General fire alarm active
fire.alarm.section Section or zone-level fire alarm active
fire.alarm.conditional Conditional fire alarm active
fire.alarm.acknowledged Fire alarm acknowledged / kvitace
fire.trouble Fire system trouble or fault
fire.test.started Fire panel, group, or address test started
fire.sounder.* Fire sounder/siren state changed
fire.routing.* Fire alarm transmission/routing state changed
fire.control.* Fire protection control output state changed

Event Structure

Every event contains:

+--------------------------------------------------+
|                    EventMessage                   |
+--------------------------------------------------+
| EventType:  "access.granted"                      |
| Severity:   "info"                                |
| AdapterId:  (which adapter sent this)             |
| NodeId:     (which device/thing)                  |
| Timestamp:  2025-01-15T10:15:03Z                  |
| Data:       {                                     |
|               "personId": "user-123",             |
|               "credentialType": "card",           |
|               "doorName": "Main Entrance"         |
|             }                                     |
+--------------------------------------------------+

Severity Levels

Severity When to use
info Normal operation (access granted, door opened)
warning Attention needed but not critical (access denied)
error Something went wrong (device error, communication failure)
critical Immediate attention required (forced entry, tamper)

When Does Adapter Create Events?

+-------------------+
| Physical Device   |
+-------------------+
         |
         | Device sends notification
         | (gRPC stream, callback, poll result)
         v
+-------------------+
| Protocol Layer    |
| (your code)       |
+-------------------+
         |
         | Transform to EventMessage
         v
+-------------------+
| Publish to        |
| OutboundEvent     |
| Channel           |
+-------------------+
         |
         | Framework handles rest
         v
+-------------------+
| NATS -> PQ System |
+-------------------+

Your adapter:

  1. Receives device notification (via streaming, polling, or callback)
  2. Transforms device-specific format to EventMessage
  3. Publishes to OutboundEventChannel

Framework handles NATS publishing automatically.


Event Data: What to Include

Include everything needed to understand what happened:

// GOOD - complete context
var eventData = new {
    personId = "user-123",        // WHO
    personName = "John Smith",
    credentialType = "card",      // HOW
    credentialId = "card-456",
    doorId = "door-789",          // WHERE
    doorName = "Main Entrance",
    timestamp = DeviceTimestamp.UtcNow,  // WHEN
    direction = "entry"           // ADDITIONAL CONTEXT
};

// BAD - missing context
var eventData = new {
    userId = "123"  // who? which door? when?
};

Events You Must Implement

Minimum events for access control adapter:

Event Trigger
access.granted Valid credential, access allowed
access.denied Valid credential, access denied (no permission)
access.unknown Unknown credential presented

Recommended additional events:

Event Trigger
door.opened Door contact sensor: open
door.closed Door contact sensor: closed
door.forced Door opened without unlock command
door.held Door open longer than allowed time

Example: Publishing an Event

// in your protocol layer when device reports access
private async Task OnDeviceAccessEvent(DeviceAccessLog log)
{
    // transform device format to EventMessage
    var eventMessage = new EventMessage(
        eventType: MapAccessResult(log.Result),  // "access.granted" etc.
        severity: DetermineSeverity(log.Result),
        adapterId: _adapterId,
        nodeId: GetDoorNodeId(log.DoorId),
        data: CreateEventData(log));

    // publish - framework handles the rest
    await _communication.PublishEventAsync(eventMessage, cancellationToken);
}

private string MapAccessResult(int deviceResult) => deviceResult switch
{
    0 => "access.granted",
    1 => "access.denied",
    2 => "access.unknown",
    _ => "access.error"
};

Events vs Polling

Two ways device can report events:

Push (Streaming/Callback)

Device ----[event]----> Adapter ----[event]----> PQ
       ----[event]---->         ----[event]---->
       ----[event]---->         ----[event]---->

+ Real-time
+ Efficient (no wasted requests)
- Requires persistent connection

Pull (Polling)

Adapter ----[request]----> Device
        <---[events]------

        (wait 5 seconds)

        ----[request]----> Device
        <---[events]------

+ Works with simple REST APIs
+ No persistent connection needed
- Delay (polling interval)
- Wasted requests when no events

Your adapter must handle whichever method device supports.


Common Mistakes

1. Publishing Status as Event

// WRONG - this is status, not event
PublishEvent("door.open", ...);  // door IS open (status)

// RIGHT - event reports change
PublishEvent("door.opened", ...);  // door WAS opened (event)
PublishStatus("door.open", ...);   // door IS open (status)

2. Missing Timestamp

// WRONG - when did this happen?
var eventData = new { personId = "123" };

// RIGHT - include timestamp from device
var eventData = new {
    personId = "123",
    timestamp = deviceEvent.Timestamp  // use device time, not now
};

3. Using Current Time Instead of Device Time

// WRONG - uses adapter's current time
timestamp = DateTimeOffset.UtcNow

// RIGHT - uses time from device (when event actually happened)
timestamp = deviceEvent.OccurredAt

Next: Status - Reporting current state