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¶
Each state below is the canonical state identifier the door reports. Strike status (secured/unsecured), deadbolt (lock.deadbolt), and door position (position.open) combine for context-aware monitoring.
Lock States (Access Control)¶
| State | Description |
|---|---|
lock.secured |
Strike secured (normal) |
lock.unsecured |
Strike released - access granted / in use |
lock.deadbolt |
Entry denied - deadbolt engaged or panel lockout, cards refused |
lock.held |
Extended unsecure (schedule/manual) |
Position State (Contact Sensor)¶
| State | Description |
|---|---|
position.open |
Door physically open |
Alarm States¶
| State | Description |
|---|---|
intrusion.forced |
Door forced while secured |
intrusion.propped |
Door held/propped open too long |
Emergency States¶
| State | Description |
|---|---|
emergency.lockdown |
Active threat lockdown |
emergency.evacuation |
Fire/emergency evacuation |
Actions¶
Strike Actions (Access Control)¶
Secured(timestamp)
- Description: Door secured
- Target State:
lock.secured - Events: none
- Usage: Strike relay deactivated, door returned to secured state
SecuredRemote(timestamp)
- Description: Door secured by operator
- Target State:
lock.secured - Events:
pq.event.access.door.secured.remote - Usage: Remote operator manually secured door
Unsecured(timestamp)
- Description: Strike released
- Target State:
lock.unsecured - Events: none
- Usage: Valid credential presented, strike relay activated
UnsecuredRemote(timestamp)
- Description: Strike released by operator
- Target State:
lock.unsecured - Events:
pq.event.access.door.unsecured.remote - Usage: Remote operator manually released door
UnsecuredByRex(timestamp)
- Description: Egress via REX
- Target State:
lock.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:
lock.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:
lock.deadbolt - Events:
pq.event.access.door.locked.remote - Usage: Remote operator locked the door
Unlocked(timestamp)
- Description: Door unlocked - normal card operation
- Target State:
lock.secured - Events: none
- Usage: Panel reports the door back in normal operation
UnlockedRemote(timestamp)
- Description: Door unlocked by operator - normal card operation
- Target State:
lock.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: none
- Parameters:
personId(Guid?) - Optional person identifier- Usage: Door contact sensor detected open state
- Notes: Automatically starts door-long-open timer if configured
Closed(timestamp) (custom)
- Description: Door closed
- Custom Logic: Clears alarm states, starts auto-relock timer
- Events: none
- Usage: Door contact sensor detected closed state
- Notes: Stops door-long-open timer, may trigger auto-relock
Alarm Actions¶
ForcedOpen(timestamp)
- Description: Forced entry
- Target State:
intrusion.forced - Events:
pq.event.access.door.forced - Usage: Door opened while secured or locked
ForcedOpenCleared(timestamp)
- Description: Forced entry cleared
- Target State:
lock.secured - Events: none
HeldOpenTooLong(timestamp)
- Description: Door held open
- Target State:
intrusion.propped - Events:
pq.event.access.door.held - Usage: Door-long-open timer expired
HeldOpenCleared(timestamp)
- Description: Door held open cleared
- Target State:
lock.unsecured - Events: none
Operational Actions¶
HoldUnsecured(timestamp)
- Description: Extended access mode
- Target State:
lock.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:
emergency.evacuation - Events:
pq.event.safety.emergency.evacuation - Usage: Fire alarm or emergency evacuation signal
EvacuationCleared(timestamp)
- Description: Evacuation cleared - normal operation
- Target State:
lock.secured - Events: none
Lockdown(timestamp)
- Description: Lockdown mode - all access denied
- Target State:
emergency.lockdown - Events:
pq.event.safety.emergency.lockdown - Usage: Active threat lockdown signal
LockdownCleared(timestamp)
- Description: Lockdown cleared - normal operation
- Target State:
lock.secured - Events: none
Framework Methods (Not in YAML)¶
GrantAccess(timeoutSeconds?, ct)
- Description: Grant temporary access with automatic state restoration
- Parameters:
timeoutSeconds(int?) - Override default timeout- Returns:
ValueTask<bool>- True if successful - Usage: Called by reader when credential validated
- Notes: Saves current state, transitions to unsecured, starts timer
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¶
public class ReaderThing : Thing
{
private readonly DoorThing _door;
public async Task HandleAccessGranted(Guid personId, CancellationToken ct)
{
// validate credential, check permissions...
// grant temporary access to door
await _door.Door.GrantAccess(timeoutSeconds: 10, ct);
// publish access event
var evt = EventBuilder.Event("pq.event.access.granted", Severity.Info)
.At(DeviceTimestamp.UtcNow, this)
.WithParameter("personId", personId)
.Build();
await PublishEvent(evt, ct);
}
}
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 identifier
if (Door.StatusState == "emergency.evacuation")
await Door.EvacuationCleared(evt.Timestamp);
else if (Door.StatusState == "emergency.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 - 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