Skip to content

HistoryPoll

Overview

Periodic device history/event polling. Many devices buffer events (access grants, alarms, tamper, door activity) in an internal log and expose them only on request, identified by a sequence number or cursor. This function has the framework call the adapter on a fixed interval to drain that buffer, translate each record into a PQ event, and advance the cursor. The adapter implements the read-and-translate logic; the framework owns the scheduling.

  • Added in: PQ Framework 2.0
  • Namespace: Pq.Adapters.Framework

When to Use

  • Devices that store events in an internal log/buffer instead of pushing them
  • Protocols with a "read events since cursor N" or "read next record" operation
  • Recovering events that occurred while the connection was down (offline buffering)
  • Any device whose event delivery is pull-based rather than push-based

If the device streams events in real time, prefer the event stream; use HistoryPoll only for buffered or pull-based logs.

States

None. HistoryPoll does not expose status states of its own. Each retrieved history record is mapped to the appropriate function action/event (Reader access events, Door activity, Input alarms, etc.).

Actions

None. Polling is driven by the framework scheduler on the configured interval; there are no action methods to invoke.

Properties

Property Type Default Range Description
history_poll_interval_seconds int 1 13600 Seconds between history polls

The default of 1 second suits devices whose log must be drained near-real-time so events are not perceived as delayed. Increase it for devices that only need periodic batch retrieval.

YAML Example

device_types:
  - type_id: AccessController
    name: "Access Control Panel"
    functions:
      HistoryPoll:
        history_poll_interval_seconds: 2
      ConnectionBase:
      Reader:
      Door:

Code Usage

The framework invokes a history-poll handler on the configured interval. Read new records since the last cursor, translate each into a function action, and persist the advanced cursor.

public partial class AccessController(Protocol protocol, ILogger<AccessController> logger)
{
    private long _lastSequence;

    // invoked by the framework on each poll tick
    public async Task PollHistory(CancellationToken ct)
    {
        if (!Connection.IsOnline)
            return; // skip when not connected

        var records = await protocol.ReadEventsSince(_lastSequence, ct);

        foreach (var record in records)
        {
            await MapToFunctionAction(record, ct);
            _lastSequence = record.Sequence; // advance cursor after each handled record
        }
    }

    private async Task MapToFunctionAction(HistoryRecord record, CancellationToken ct)
    {
        switch (record.Kind)
        {
            case RecordKind.AccessGranted:
                await Reader.AccessGranted(record.Timestamp, ct);
                break;
            case RecordKind.DoorForced:
                await Door.ForcedOpen(record.Timestamp, ct);
                break;
            // ... remaining record kinds
        }
    }
}

Notes

  • Framework-Scheduled: You do not start your own timer; the framework calls the handler at history_poll_interval_seconds
  • Cursor Persistence: Advance and persist the cursor only after a record is successfully handled, so a crash does not lose or duplicate events
  • Use Device Timestamps: Map each record's original device timestamp onto the published event, not the time it was read
  • Offline Recovery: On reconnect, the buffered backlog is drained in order from the last cursor
  • Skip When Offline: Guard the poll body so it does no work when the device is not connected
  • Order Matters: Process records in sequence order to preserve event causality

See Also

  • Functions: StatusPoll - Periodic status reads
  • Functions: ConnectionBase - Connection lifecycle
  • Functions: Reader - Credential reader events
  • Functions: Door - Access control door events