Skip to content

OpenLatch System Diagrams

Door State Machine

stateDiagram-v2
    [*] --> Locked : Power on / Boot

    Locked --> Validating : NFC card scanned
    Locked --> ShutdownCountdown : Shutdown button pressed (inside)
    Locked --> Locked : Shutdown countdown complete

    Validating --> CheckingSchedule : UID found in allow-list
    Validating --> Denied : UID not found
    Validating --> WaitingApproval : CONDITIONAL key + network available
    Validating --> Denied : CONDITIONAL key + no network

    CheckingSchedule --> Unlocking : UNRESTRICTED / within time slot
    CheckingSchedule --> Unlocking : EMERGENCY (queue notification)
    CheckingSchedule --> GraceUnlock : Within grace period
    CheckingSchedule --> LockOnly : Expired but within grace
    CheckingSchedule --> Denied : Outside time slot / expired

    WaitingApproval --> Unlocking : Organizer approves
    WaitingApproval --> Denied : Organizer denies / timeout (60s)

    Unlocking --> Unlocked : NUKI confirms unlatch
    Unlocking --> Error : NUKI BLE command failed

    GraceUnlock --> Unlocked : NUKI confirms unlatch
    LockOnly --> Locking : NUKI lock command sent

    Unlocked --> AutoRelockWait : Auto-relock timer starts (5s)
    Unlocked --> Locking : NFC card scanned (manual lock)

    AutoRelockWait --> Locking : Timer expired
    AutoRelockWait --> AutoRelockWait : NFC card scanned (reset timer)

    Locking --> Locked : NUKI confirms locked
    Locking --> Error : NUKI BLE command failed

    Denied --> Locked : Red LED + buzzer (2s)
    Denied --> EscalationWait : 3rd denied scan within 60s
    EscalationWait --> Locked : Timeout (no code entered)
    EscalationWait --> Unlocking : Valid TOTP/HOTP code on keypad
    EscalationWait --> Denied : Invalid code (rate limited after 3 fails)
    Error --> Locked : Error LED pattern (5s)

    ShutdownCountdown --> Locked : Countdown canceled (button again)
    ShutdownCountdown --> Locking : Countdown finished (120s)

    note right of Validating
        Lookup UID in RAM allow-list
        Check access_type
    end note

    note right of WaitingApproval
        ESP32 connects to network
        POST /api/v1/device/approve
        Waits up to 60 seconds
        LED blinks blue
    end note

    note right of EscalationWait
        LED blinks yellow
        Waiting for guarantor
        TOTP code on keypad
    end note

    note right of GraceUnlock
        Grace period active
        Queue anomaly notification
        if NOTIFY_ON_ANOMALY set
    end note

    note left of ShutdownCountdown
        Space shutdown sequence
        Buzzer beeps every 5s
        LED flashes yellow
        People exit the space
    end note

Communication Architecture

flowchart TB
    subgraph Outside ["Outside the Space"]
        NFC[/"CONLAN M1200\nNFC Terminal\n(IP67 outdoor)"/]
        CARD((NFC Card\nMIFARE DESFire))
    end

    subgraph Door ["Door Frame"]
        NUKI["NUKI Smart Lock\nUltra\n(battery powered)"]
    end

    subgraph Inside ["Inside the Space"]
        ESP["ESP32\nController"]
        BTN[/"Shutdown\nButton"/]
        LED[/"LED + Buzzer\nFeedback"/]
    end

    subgraph Network ["Network"]
        WIFI{{"WiFi\nAccess Point"}}
    end

    subgraph Server ["Backend Server"]
        API["FastAPI\nBackend"]
        DB[("PostgreSQL\nDatabase")]
        ADMIN[/"Admin UI\n(Web)"/]
    end

    subgraph External ["External Services"]
        ICS[/"ICS Calendar\nFeed"/]
        NOTIFY[/"Notification\nService"/]
    end

    CARD -- "DESFire\nchallenge-response\n(contactless)" --> NFC
    NFC -- "Wiegand 26/34-bit\n(D0/D1 wires)" --> ESP
    ESP -- "BLE\n(lock/unlock/unlatch\ncommands)" --> NUKI
    BTN -- "GPIO\n(digital input)" --> ESP
    ESP -- "GPIO\n(digital output)" --> LED

    ESP -- "HTTPS poll\nevery 5 min\n(ESP32 initiates)" --> WIFI
    WIFI -- "TCP/IP" --> API

    API -- "SQL" --> DB
    ADMIN -- "REST API\n(JWT auth)" --> API
    ICS -- "HTTP GET\n(periodic fetch)" --> API
    API -- "Push / SMS / Email" --> NOTIFY

    style ESP fill:#2d5016,color:#fff
    style NUKI fill:#1a3a5c,color:#fff
    style NFC fill:#5c3a1a,color:#fff
    style API fill:#3a1a5c,color:#fff
    style DB fill:#1a5c5c,color:#fff

Data Flow: Allow-List Sync

sequenceDiagram
    participant ESP as ESP32
    participant WIFI as WiFi AP
    participant API as Backend API
    participant DB as PostgreSQL

    Note over ESP: Every 5 minutes
    ESP->>ESP: Disconnect BLE (release radio)
    ESP->>WIFI: Connect (STA mode)
    WIFI-->>ESP: IP assigned

    ESP->>API: GET /api/v1/device/allowlist?current_version=42
    Note over ESP,API: Authorization: Bearer <device-token>

    API->>DB: Fetch current allow-list version

    alt No update needed
        API-->>ESP: 304 Not Modified
    else New version available
        API->>DB: Fetch pre-signed binary payload
        API-->>ESP: 200 OK (pre-signed binary payload)
        ESP->>ESP: Verify sub-key certificate (master key)
        ESP->>ESP: Verify payload signature (sub-key)
        ESP->>ESP: Check version > current
        ESP->>ESP: Write to LittleFS
        ESP->>ESP: Reload allow-list into RAM
    end

    opt Queued events to report
        ESP->>API: POST /api/v1/device/events
        Note over ESP,API: Access logs, anomalies, emergency alerts
    end

    ESP->>WIFI: Disconnect
    ESP->>ESP: Re-enable BLE (NUKI)

Data Flow: Denied Card Escalation

sequenceDiagram
    participant M as Member
    participant T as NFC Terminal + Keypad
    participant ESP as ESP32
    participant API as Backend API
    participant G as Guarantor

    M->>T: Scan card (1st)
    T->>ESP: Wiegand: card UID
    ESP->>ESP: Lookup UID → SCHEDULED, outside time slot
    ESP-->>M: Red LED + buzzer (DENIED)
    ESP->>ESP: record_denial(uid, count=1)

    M->>T: Scan card (2nd, within 60s)
    T->>ESP: Wiegand: card UID
    ESP-->>M: Red LED + buzzer (DENIED)
    ESP->>ESP: record_denial(uid, count=2)

    M->>T: Scan card (3rd, within 60s)
    T->>ESP: Wiegand: card UID
    ESP-->>M: Yellow LED blink (ESCALATION)
    ESP->>ESP: record_denial(uid, count=3) → escalated!

    ESP->>API: POST /api/v1/device/events [{escalation_triggered, uid}]
    Note over API: Event queued (group notification: future)

    M->>G: Member calls guarantor by phone
    Note over M,G: Member explains situation
    G->>G: Open authenticator app → read TOTP code
    Note over G,M: Guarantor tells key-ID + code by phone

    M->>T: Keypad: 5#287082#
    T->>ESP: Wiegand: 4-bit per keypress
    ESP->>ESP: Lookup key_id=5 → guarantor entry
    ESP->>ESP: Verify TOTP (window T-10 to T+1)
    ESP->>ESP: Code valid → GRANTED
    ESP-->>M: Green LED + beep
    ESP->>ESP: clear_denial(uid)

Data Flow: Conditional Access (Backup Keyholder)

sequenceDiagram
    participant USER as Backup Keyholder
    participant T as NFC Terminal + Keypad
    participant ESP as ESP32
    participant API as Backend API
    participant G as Guarantor (Phone)

    USER->>T: Present NFC card
    T->>ESP: Wiegand: card UID
    ESP->>ESP: Lookup UID → ACCESS_CONDITIONAL

    Note over ESP: LED blinks blue = "awaiting approval"

    opt Network available
        ESP->>API: POST /api/v1/device/events [{conditional_request, uid}]
        Note over API: Event queued (group notification: future)
    end

    USER->>G: Keyholder calls guarantor by phone
    Note over USER,G: Keyholder explains situation
    G->>G: Open authenticator app → read TOTP code
    Note over G,USER: Guarantor tells key-ID + code by phone

    USER->>T: Keypad: 5#287082#
    T->>ESP: Wiegand: 4-bit per keypress
    ESP->>ESP: Lookup key_id=5 → guarantor entry
    ESP->>ESP: Verify TOTP (window T-10 to T+1)
    ESP->>ESP: Code valid → GRANTED
    ESP->>ESP: NUKI BLE → unlatch
    Note over ESP: Green LED + beep

Data Flow: Emergency Access (Landlord)

sequenceDiagram
    participant LL as Landlord
    participant NFC as NFC Terminal
    participant ESP as ESP32
    participant NUKI as NUKI Lock
    participant API as Backend API
    participant ADMINS as Admins (Phone/Email)

    LL->>NFC: Present emergency NFC card
    NFC->>ESP: Wiegand: card UID
    ESP->>ESP: Lookup UID → ACCESS_EMERGENCY
    ESP->>NUKI: BLE: unlatch
    NUKI-->>ESP: Confirmed
    Note over ESP: Green LED + beep (immediate access)

    par Notify immediately
        ESP->>ESP: Connect to network
        ESP->>API: POST /api/v1/device/events [{emergency_access, uid, ts}]
        API->>ADMINS: URGENT: Emergency key used by Landlord at 14:32
    end

Space Shutdown Sequence

sequenceDiagram
    participant P as Person Inside
    participant BTN as Shutdown Button
    participant ESP as ESP32
    participant LED as LED + Buzzer
    participant NUKI as NUKI Lock

    P->>BTN: Press shutdown button
    ESP->>LED: Yellow LED flashing + beep
    Note over ESP: 120s countdown starts

    loop Every 5 seconds
        ESP->>LED: Beep (increasingly urgent)
    end

    alt Cancel
        P->>BTN: Press button again
        ESP->>LED: Cancel confirmation (white flash)
        Note over ESP: Countdown canceled
    else Countdown complete
        ESP->>NUKI: BLE: lock
        NUKI-->>ESP: Confirmed
        ESP->>LED: Solid green + long beep
        ESP->>ESP: Log: locked_by_button
    end