Skip to content

Event Types

Understanding the three types of messages that flow through your adapter.


What Are Message Types?

Your adapter receives messages from physical devices. Not all messages are the same:

Physical Device
    |
    +-- "Card 12345 granted access"     (Audit Event)
    +-- "Door is now unlocked"           (Status Update)
    +-- "ACK command received"           (Control Message)
    |
    v
Your Adapter
    |
    v
Different handling for each type

Three fundamentally different message types require different handling.


The Three Types

Type What it represents Must process? Becomes PqEvent?
Audit Log Event Something happened YES - ALL YES
Status Update Current state YES NO
Control Message Protocol communication NO NO

1. Audit Log Events

What They Are

Events that report something that already happened on the device. These are historical records required for compliance, security audits, and ABAC rule evaluation.

Examples:
- Access granted to John Smith with card 12345
- Door forced open without authorization
- Alarm triggered on zone 3
- User enrolled fingerprint
- Access denied due to invalid time zone

Critical Rule: Process ALL Events

YOU MUST PROCESS EVERY AUDIT EVENT THE DEVICE REPORTS. NEVER SILENTLY DROP EVENTS.

Why:

  • Compliance - regulatory requirements (GDPR, HIPAA, SOX) demand complete audit trails
  • ABAC Rules - PQ system may have rules triggered by ANY event type
  • Security - missing events create security blind spots
  • Forensics - incomplete logs make incident investigation impossible
// WRONG - silently ignoring events you don't recognize
if (deviceEvent.Type == KnownEventType.AccessGranted)
{
    await ProcessAccessEvent(deviceEvent);
}
// Unknown events just disappear - NEVER DO THIS!

// RIGHT - process ALL events
if (deviceEvent.Type == KnownEventType.AccessGranted)
{
    await ProcessAccessEvent(deviceEvent);
}
else
{
    // publish as PqEvents.Unknown - let PQ decide what to do
    await PublishUnknownEvent(deviceEvent);
}

How to Handle

Audit events get routed to Thing handlers:

Device Event
    |
    v
EventRouter.Dispatch()
    |
    v
Routes match event to Thing
    |
    v
Thing handler method
    |
    v
Publishes PqEvent (access.granted, door.forced, etc.)

Example from access control:

// in your EventRouting configuration
router.When(e => e.EventType == 5)
    .WithAddress<ReaderDevice, uint>(e => e.ReaderId)
    .WithPerson(e => e.CardHolderId.ToString())
    .WithCredential(e => e.CardNumber.ToString())
    .Handle((reader, personId, credentialId, e) =>
        reader.AccessGranted(
            DeviceTimestamp.FromUnixSeconds(e.UnixTimestamp),
            personId: personId,
            credentialId: credentialId));

Thing handler publishes PqEvent:

public partial class ReaderDevice : Thing
{
    public Task AccessGranted(DeviceTimestamp timestamp, Guid? personId, Guid? credentialId)
    {
        return PublishPqEvent(
            PqEvents.AccessGranted,
            timestamp,
            data: new { personId, credentialId });
    }
}

Common Audit Events

Category Examples
Access access.granted, access.denied, access.unknown
Door door.opened, door.closed, door.forced, door.held
Alarm alarm.triggered, alarm.cleared, alarm.tamper
Fire fire.alarm, fire.alarm.general, fire.trouble, fire.sounder.silenced, fire.routing.started
User user.enrolled, user.deleted, credential.added
System device.reboot, configuration.changed, time.synchronized

2. Status Updates

What They Are

Messages that report the current state of the device or component. Status describes "what is" right now, not "what happened."

Examples:
- Device connection: online
- Door lock state: unlocked
- Input state: open
- Function status: ready

See Status concept for complete details.

Key Differences from Events

EVENT:  "Door was opened at 10:15:03"   (point in time, historical)
STATUS: "Door is open"                   (current state, continuous)

EVENT:  "Access granted to John"         (one-time occurrence)
STATUS: "Reader online"                  (ongoing state)

How to Handle

Status updates publish to OutboundStatusChannel:

public async Task OnDeviceStatusChange(DeviceStatus status)
{
    var statusMessage = new StatusMessage(
        adapterId: _adapterId,
        nodeId: deviceId,
        data: JsonSerializer.SerializeToElement(new {
            connection = status.IsOnline ? "online" : "offline",
            lockState = status.IsLocked ? "locked" : "unlocked"
        }));

    await _communication.PublishStatusAsync(statusMessage, ct);
}

Status does NOT become a PqEvent. Status is queryable state, events are historical records.


3. Control Messages

What They Are

Protocol-specific messages used for communication flow control. These are implementation details of the protocol, not business events.

Examples:
- ACK (acknowledgment)
- NAK (negative acknowledgment)
- Keepalive/heartbeat
- Handshake messages
- Protocol version negotiation
- Ping/pong

How to Handle

Handle internally in your protocol layer. NEVER publish control messages as PqEvents.

// in your protocol message handler
private async Task OnMessageReceived(ProtocolMessage msg)
{
    switch (msg.Type)
    {
        case MessageType.Keepalive:
            // respond to keepalive, don't publish event
            await SendAck(msg.SequenceNumber);
            break;

        case MessageType.AccessEvent:
            // this is an audit event - route to EventRouter
            await _eventRouter.Dispatch(msg.ToAccessEvent(), ct);
            break;

        case MessageType.StatusUpdate:
            // this is status - publish to status channel
            await PublishDeviceStatus(msg.ToStatus());
            break;
    }
}

Control messages are consumed by the protocol layer and never leave the adapter.


Decision Tree: What Type Is This Message?

Device sent a message
        |
        v
   Does it report something
   that already happened?
        |
   +----+----+
   |         |
  YES       NO
   |         |
   v         v
Is it an  Is it current
occurrence state information?
once?         |
   |     +----+----+
   |     |         |
   |    YES       NO
   |     |         |
   v     v         v
AUDIT  STATUS   CONTROL
EVENT  UPDATE   MESSAGE
   |     |         |
   v     v         v
Route  Publish   Handle
to     to        in
Thing  Status    Protocol
       Channel   Layer
   |     |         |
   v     |         v
PqEvent |      (consumed)
        v
   (queryable)

Mixed Message Protocols

Some protocols embed multiple message types in a single stream:

Example: Access Control Panel Events

Event Type 5: Access granted         -> AUDIT EVENT
Event Type 9: Door state             -> STATUS UPDATE (but also audit!)
Event Type 7: Input changed          -> Could be either
ACK responses                        -> CONTROL MESSAGE

When a message has dual nature (both event and status), framework handles it automatically when you use function methods:

// door state change is BOTH event and status
router.When(e => e.EventType == EventCode.DoorOpened)
    .WithAddress<DoorController, int>(e => e.DoorId)
    .Handle((door, evt) =>
        // Framework automatically:
        // 1. Publishes audit event (PqEvents.DoorOpened)
        // 2. Updates function state (Door.State = "open")
        // 3. Updates status for PQ UI
        door.Door.Opened(
            DeviceTimestamp.FromUnixSeconds(evt.Timestamp),
            CancellationToken.None));

You don't need to handle status separately - function methods publish both event and status automatically.

Example: Keepalive with Status

Some devices embed status in keepalive messages:

private async Task OnKeepalive(KeepaliveMessage msg)
{
    // 1. Handle keepalive (control message)
    _lastKeepalive = timeProvider.GetUtcNow();

    // 2. Extract embedded status
    if (msg.HasStatusData)
    {
        await PublishStatus(new {
            cpuLoad = msg.CpuPercent,
            memoryUsed = msg.MemoryBytes,
            uptime = msg.UptimeSeconds
        });
    }

    // 3. NO PqEvent - keepalive itself is not an audit event
}

Why ALL Audit Events Matter

Compliance Requirements

Many industries require complete audit trails:

HIPAA (Healthcare):
- Every access to patient records must be logged
- Missing events = compliance violation = fines

GDPR (Privacy):
- Complete record of who accessed personal data
- Right to audit requires full history

SOX (Financial):
- Complete access logs for financial systems
- Missing events compromise audit integrity

ABAC Rule Examples

PQ system may have rules triggered by ANY event:

Rule: "Alert security if >5 access denied events in 10 minutes"
      -> Requires ALL denied events, even from unknown cards

Rule: "Disable credential after 3 failed attempts"
      -> Missing one attempt breaks the rule

Rule: "Log all tamper events regardless of source"
      -> Even unknown device types must report tampers

Unknown Events

If you receive an event you don't recognize:

// publish as PqEvents.Unknown with raw data
router.Unhandled(async (evt, ct) =>
{
    await PublishPqEvent(
        PqEvents.Unknown,
        DeviceTimestamp.FromEvent(evt),
        severity: "warning",
        data: new {
            rawEventType = evt.Type,
            rawEventCode = evt.Code,
            rawData = evt.RawBytes,
            message = $"Unknown event type {evt.Type} from device"
        });
});

PQ system administrators can:

  • See unknown events in audit log
  • Create ABAC rules for them
  • Request adapter enhancement to handle them properly

Summary

Type Process? Route to Thing? Publish to PQ? Used for
Audit Event YES - ALL YES YES (PqEvent) Compliance, ABAC, forensics
Status Update YES NO YES (Status) Current state, monitoring
Control Message YES NO NO Protocol communication

Key Takeaways

  1. Never drop audit events - process ALL events from device
  2. Events become PqEvents - routed through EventRouter to Things
  3. Status is separate - current state, not historical
  4. Control stays internal - protocol layer only, never published
  5. When in doubt - publish as PqEvents.Unknown, let PQ decide

Related: