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 |
1–3600 |
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