Door¶
Overview¶
Physical access control door with combined strike/deadbolt control, position sensing, and emergency override modes. Supports intelligent state management with framework-managed timers for door-long-open detection and auto-relock.
- Added in: PQ Framework 2.0
- Namespace:
Pq.Adapters.Framework
When to Use¶
- Access control doors with electric strike
- Doors with motorized deadbolt locks
- Doors with position sensors (door contact/DPS)
- Doors with REX (request-to-exit) detection
- Emergency evacuation/lockdown door control
- Any door requiring access grant/deny logic
States¶
State is the generated DoorState enum member the adapter code uses; Status id is the canonical identifier the door reports. Strike status (secured/unsecured), deadbolt (lock.deadbolt), and door position (position.open) combine for context-aware monitoring. Secured is the default state the framework fills in when the door reports nothing yet.
Lock States (Access Control)¶
| State | Status id | Description |
|---|---|---|
Secured |
lock.secured |
Strike secured (normal) |
Unsecured |
lock.unsecured |
Strike released - access granted / in use |
Deadbolt |
lock.deadbolt |
Entry denied - deadbolt engaged or panel lockout, cards refused |
Held |
lock.held |
Extended unsecure (schedule/manual) |
Position State (Contact Sensor)¶
| State | Status id | Description |
|---|---|---|
Open |
position.open |
Door physically open |
Alarm States¶
| State | Status id | Description |
|---|---|---|
Forced |
intrusion.forced |
Door forced while secured |
Propped |
intrusion.propped |
Door held/propped open too long |
Emergency States¶
| State | Status id | Description |
|---|---|---|
Lockdown |
emergency.lockdown |
Active threat lockdown |
Evacuation |
emergency.evacuation |
Fire/emergency evacuation |
Actions¶
Strike Actions (Access Control)¶
Secured(timestamp)
- Description: Door secured
- Target State:
Secured - Events: none
- Usage: Strike relay deactivated, door returned to secured state
SecuredRemote(timestamp)
- Description: Door secured by operator
- Target State:
Secured - Events:
pq.event.access.door.secured.remote - Usage: Remote operator manually secured door
Unsecured(timestamp)
- Description: Strike released
- Target State:
Unsecured - Events: none
- Usage: Valid credential presented, strike relay activated
UnsecuredRemote(timestamp)
- Description: Strike released by operator
- Target State:
Unsecured - Events:
pq.event.access.door.unsecured.remote - Usage: Remote operator manually released door
UnsecuredByRex(timestamp)
- Description: Egress via REX
- Target State:
Unsecured - Events: none
- Usage: REX (request-to-exit) button pressed or motion detected on egress side
Lock Actions (Entry Denied / Restored)¶
Locked means entry is denied, cards included — whether the vendor implements it as a motorized deadbolt, a panel lockout, or a "disable door" mode is a wire detail the door state does not distinguish.
Locked(timestamp)
- Description: Door locked - entry denied, cards included
- Target State:
Deadbolt - Events: none
- Usage: Panel reports the door locked (deadbolt extended, lockout engaged)
LockedRemote(timestamp)
- Description: Door locked by operator - entry denied, cards included
- Target State:
Deadbolt - Events:
pq.event.access.door.locked.remote - Usage: Remote operator locked the door
Unlocked(timestamp)
- Description: Door unlocked - normal card operation
- Target State:
Secured - Events: none
- Usage: Panel reports the door back in normal operation
UnlockedRemote(timestamp)
- Description: Door unlocked by operator - normal card operation
- Target State:
Secured - Events:
pq.event.access.door.unlocked.remote - Usage: Remote operator returned the door to normal operation
Position Actions (Contact Sensor)¶
Opened(timestamp, personId?) (custom)
- Description: Door opened
- Custom Logic: Detects forced entry if door secured/locked
- Events:
pq.event.access.door.opened - Parameters:
personId(Guid?) - Optional person identifier- Usage: Door contact sensor detected open state
- Notes: Automatically starts door-long-open timer if configured. The position event is published in every case - leaf position is an audit fact independent of the lock and alarm state
Closed(timestamp) (custom)
- Description: Door closed
- Custom Logic: Clears alarm states, starts auto-relock timer
- Events:
pq.event.access.door.closed - Usage: Door contact sensor detected closed state
- Notes: Stops door-long-open timer, may trigger auto-relock. The position event is published in every
case, including while a forced/lockdown/evacuation alarm stays latched. Closing does not clear a latched
alarm - route the vendor's own restore event to
ForcedOpenCleared/HeldOpenClearedinstead
Alarm Actions¶
ForcedOpen(timestamp) (custom)
- Description: Forced entry
- Target State:
Forced - Events:
pq.event.access.door.forced - Usage: Door opened while secured or locked
- Notes: The alarm latches - calling it again while the door is already in
intrusion.forcedpublishes nothing. Devices that report forced entry natively reach this action twice (once via their own event, once via the position route that infers it) and still produce a single audit event
ForcedOpenCleared(timestamp)
- Description: Forced entry cleared
- Target State:
Secured - Events:
pq.event.access.door.forced.cleared - Usage: Vendor reported the forced-entry condition restored
HeldOpenTooLong(timestamp)
- Description: Door held open
- Target State:
Propped - Events:
pq.event.access.door.held - Usage: Door-long-open timer expired
HeldOpenCleared(timestamp)
- Description: Door held open cleared
- Target State:
Unsecured - Events:
pq.event.access.door.held.cleared - Usage: Vendor reported the held-open condition restored
Operational Actions¶
HoldUnsecured(timestamp)
- Description: Extended access mode
- Target State:
Held - Events:
pq.event.access.door.held.extended - Usage: Schedule or manual command to keep door unsecured
- Notes: Disables door-long-open timer
Emergency Actions¶
Evacuation(timestamp)
- Description: Evacuation mode - all egress unsecured
- Target State:
Evacuation - Events:
pq.event.safety.emergency.evacuation - Usage: Fire alarm or emergency evacuation signal
EvacuationRemote(timestamp)
- Description: Evacuation mode triggered remotely
- Target State:
Evacuation - Events:
pq.event.safety.emergency.evacuation.remote - Usage: Evacuation signal issued from a management system or central station
EvacuationCleared(timestamp)
- Description: Evacuation cleared - normal operation
- Target State:
Secured - Events:
pq.event.safety.emergency.evacuation.cleared
EvacuationClearedRemote(timestamp)
- Description: Evacuation cleared remotely - normal operation
- Target State:
Secured - Events:
pq.event.safety.emergency.evacuation.cleared.remote
Lockdown(timestamp)
- Description: Lockdown mode - all access denied
- Target State:
Lockdown - Events:
pq.event.safety.emergency.lockdown - Usage: Active threat lockdown signal
LockdownRemote(timestamp)
- Description: Lockdown mode triggered remotely
- Target State:
Lockdown - Events:
pq.event.safety.emergency.lockdown.remote - Usage: Lockdown signal issued from a management system or central station
LockdownCleared(timestamp)
- Description: Lockdown cleared - normal operation
- Target State:
Secured - Events:
pq.event.safety.emergency.lockdown.cleared
LockdownClearedRemote(timestamp)
- Description: Lockdown cleared remotely - normal operation
- Target State:
Secured - Events:
pq.event.safety.emergency.lockdown.cleared.remote
Framework Methods (Not in YAML)¶
ReleaseDoor(timeoutSeconds?, ct)
- Description: Releases the door lock by switching to unsecured state. After the timeout (or door close plus auto-relock) the door returns to the previous secured state.
- Parameters:
timeoutSeconds(int?) - Override timeout, or null to use theaccess_grant_timeout_secondsproperty- Returns:
ValueTask<bool>- True if the state was changed successfully - Usage: Called by the reader when a credential is validated
- Notes: With
use_framework_timers: trueit saves the current state, transitions to unsecured, calls the hardware-unlock hook, and starts the restoration timer
NotifyDeniedAttempt(ct)
- Description: Publishes a timed flash status indicating a denied access attempt
- Returns:
ValueTask - Notes: The status appears under a separate aggregator key and auto-clears after the access-grant timeout
Properties¶
| Property | Type | Default | Description |
|---|---|---|---|
use_framework_timers |
bool | true | True = framework manages timers, False = device manages (private/internal) |
doorlongopen_timeout_seconds |
int | 45 | Seconds before doorlongopen alarm |
auto_relock_timeout_seconds |
int | 5 | Seconds to wait before auto-relock after unsecure |
access_grant_timeout_seconds |
int | 5 | Seconds to keep door unsecured after access grant |
has_door_contact |
bool | false | Device has a door contact sensor reporting open/close events (private/internal) |
has_lock_monitor |
bool | false | Device reports lock state changes (lock/unlock events) (private/internal) |
There is no has_rex_sensor flag: REX is the UnsecuredByRex action (and, where REX is a separate
input terminal, a deviceRef from the door — see concepts/access-control-assembly.md), not a Door property.
YAML Example¶
device_types:
- type_id: AccessDoor
name: "Access Control Door"
category: door
functions:
Door:
properties:
has_door_contact: true
has_lock_monitor: false
doorlongopen_timeout_seconds: 60
auto_relock_timeout_seconds: 5
access_grant_timeout_seconds: 10
- type_id: HighSecurityDoor
name: "High Security Door with Deadbolt"
category: door
functions:
Door:
Tamper:
properties:
has_door_contact: true
has_lock_monitor: true
use_framework_timers: true
doorlongopen_timeout_seconds: 30
Code Usage¶
Basic Door Thing¶
public class DoorThing : Thing
{
public DoorFunction Door { get; }
public DoorThing(Thing parent) : base(parent)
{
Door = new DoorFunction(this);
}
protected override async Task HandleDeviceEvent(DoorEvent evt, CancellationToken ct)
{
switch (evt.Type)
{
case DoorEventType.Secured:
await Door.Secured(evt.Timestamp);
break;
case DoorEventType.Unsecured:
await Door.Unsecured(evt.Timestamp);
break;
case DoorEventType.DoorOpened:
await Door.Opened(evt.Timestamp);
break;
case DoorEventType.DoorClosed:
await Door.Closed(evt.Timestamp);
break;
}
}
}
Access Grant Integration¶
A reader does not publish the access event itself. ReaderFunction.AccessGranted(...) publishes pq.event.access.granted and releases the associated doors in one call — the framework resolves the doors from the device tree.
public class ReaderThing : Thing
{
public ReaderThing(Thing parent) : base(parent) => Reader = new ReaderFunction(this);
public ReaderFunction Reader { get; }
public async Task HandleAccessGranted(DeviceTimestamp timestamp, Guid personId, Guid credentialId)
{
// one call: publishes pq.event.access.granted and releases the associated doors
await Reader.AccessGranted(
timestamp,
credentialId: credentialId,
credentialType: "card",
direction: "in",
personId: personId);
}
}
Call Door.ReleaseDoor(...) directly only for a release that carries no access decision, such as an operator command or a request-to-exit button.
Hardware Hooks¶
public class DoorThing : Thing
{
public DoorThing(Thing parent) : base(parent)
{
Door = new DoorFunction(this);
// configure hardware callbacks
Door.Hooks.OnAccessGrantRequired = async ct =>
{
// send unsecure command to hardware
await Protocol.SendUnsecureCommand(ct);
};
Door.Hooks.OnAutoRelockRequired = async ct =>
{
// send secure command to hardware
await Protocol.SendSecureCommand(ct);
};
Door.Hooks.OnAccessGrantExpired = async ct =>
{
// restore hardware to secured state
await Protocol.SendSecureCommand(ct);
await Door.Secured(DeviceTimestamp.UtcNow);
};
}
}
REX and Emergency Modes¶
protected override async Task HandleDeviceEvent(DoorEvent evt, CancellationToken ct)
{
switch (evt.Type)
{
case DoorEventType.RexDetected:
await Door.UnsecuredByRex(evt.Timestamp);
break;
case DoorEventType.FireAlarm:
await Door.Evacuation(evt.Timestamp);
break;
case DoorEventType.LockdownSignal:
await Door.Lockdown(evt.Timestamp);
break;
case DoorEventType.EmergencyCleared:
// determine which mode to clear based on the current state
if (Door.StatusState == DoorState.Evacuation)
await Door.EvacuationCleared(evt.Timestamp);
else if (Door.StatusState == DoorState.Lockdown)
await Door.LockdownCleared(evt.Timestamp);
break;
}
}
Forced Entry Detection¶
// framework handles this automatically in Opened() method
protected override async Task HandleDeviceEvent(DoorEvent evt, CancellationToken ct)
{
switch (evt.Type)
{
case DoorEventType.DoorOpened:
// if door is secured/locked, Opened() detects forced entry automatically
await Door.Opened(evt.Timestamp);
break;
case DoorEventType.DoorClosed:
// closes forced entry alarm and starts auto-relock
await Door.Closed(evt.Timestamp);
break;
}
}
Device-Managed Timers¶
device_types:
- type_id: SmartDoor
functions:
Door:
properties:
use_framework_timers: false # device manages its own timers
// in code - no timer management needed
protected override async Task HandleDeviceEvent(DoorEvent evt, CancellationToken ct)
{
switch (evt.Type)
{
case DoorEventType.DoorLongOpen:
// device sends this event when its internal timer expires
await Door.HeldOpenTooLong(evt.Timestamp);
break;
case DoorEventType.AutoRelocked:
// device sends this event after its auto-relock timer
await Door.Secured(evt.Timestamp);
break;
}
}
Notes¶
- Combined State Model: Door state combines strike status (secured/unsecured) with position (open/closed) for context-aware monitoring
- Strike vs Deadbolt: Strike (
secured/unsecured) is electric relay for access control, deadbolt (locked) is motorized physical lock - Framework Timers: When
use_framework_timers=true, framework manages door-long-open, auto-relock, and access-grant timers - Device-Managed Timers: When
use_framework_timers=false, device handles timing and sends events when timeouts occur - Forced Entry Logic:
Opened()method automatically detects forced entry when door opens while inlock.securedorlock.deadbolt; the alarm latches, so a native vendor forced event arriving alongside it does not duplicate the audit event - Alarm Clearing: A latched alarm is cleared by
ForcedOpenCleared/HeldOpenCleared, driven by the vendor's own restore event - never derived from a status read-back or inferred from the door closing - Auto-Relock Behavior: After
Closed(), if door was unsecured/open or inintrusion.propped, auto-relock timer starts - REX Detection: Request-to-exit (REX) allows egress without credential, typically PIR or push button on secure side
- Emergency Overrides: Evacuation/lockdown states override normal access control logic
- Hooks Pattern: Adapter can register hardware callbacks via
Door.Hooksfor access grant, auto-relock, and grant expiration - IAsyncDisposable: DoorFunction implements disposal for cleaning up active timers