Skip to content

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 in lock.secured or lock.deadbolt
  • Auto-Relock Behavior: After Closed(), if door was unsecured/open or in intrusion.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.Hooks for access grant, auto-relock, and grant expiration
  • IAsyncDisposable: DoorFunction implements disposal for cleaning up active timers

See Also

  • Functions: Reader - Credential reader integration
  • Functions: Output - Generic relay control