Skip to content

ADR-004: Offline-First ESP32 Architecture

Status: Accepted Date: 2026-02-25

Context

The ESP32 controller at the door must make access decisions reliably. WiFi connectivity at the hackerspace can be intermittent — the router may reboot, the ISP may have outages, or the backend server may be down for maintenance. The door must continue to function.

Additionally, the ESP32 has a single radio shared between WiFi and BLE. It cannot use both simultaneously, and it needs BLE for controlling the NUKI Smart Lock Ultra.

Options Considered

A) Online-First (query backend for each access decision)

  • Pro: Always up-to-date access decisions
  • Pro: No local storage needed
  • Con: Door is unusable when WiFi or backend is down
  • Con: Added latency for each card scan (WiFi connect + HTTP request + WiFi disconnect)
  • Con: WiFi/BLE radio contention — NUKI commands blocked during HTTP requests

B) Hybrid (local cache + real-time verification when possible)

  • Pro: Works offline, benefits from online when available
  • Con: Complex logic to decide when to go online
  • Con: Higher WiFi usage, more BLE/WiFi switching

C) Offline-First (periodic sync of signed allow-list)

  • Pro: All decisions are local and fast (<10ms)
  • Pro: Works without any network connectivity
  • Pro: Predictable WiFi usage pattern (every 5 minutes)
  • Pro: BLE always available for NUKI control except during sync windows
  • Con: Up to 5 minutes of staleness after a key is revoked
  • Con: Cannot enforce real-time rules without going online

Decision

Offline-First (Option C) with a Conditional exception for access types that explicitly require online approval.

The ESP32 stores a signed allow-list in LittleFS flash and loads it into RAM on boot. Every 5 minutes, it connects to WiFi, fetches updates, and disconnects. Between syncs, all access decisions use the cached list.

The one exception is ACCESS_CONDITIONAL, where the entry explicitly requires real-time backend approval (e.g., a backup keyholder who needs an organizer's confirmation). In this case, the ESP32 connects to WiFi on-demand for a single HTTP request.

Network Connectivity

Although the ESP32 works offline for access decisions, it requires periodic network access for allow-list sync, event reporting, and time synchronization. The default transport is WiFi, but wired Ethernet is strongly recommended for production (exterior/main doors) for reliability. ESP32 boards with built-in Ethernet (e.g., WT32-ETH01, Olimex ESP32-POE) eliminate WiFi dependency entirely and avoid the WiFi/BLE radio contention described below.

WiFi is acceptable for:

  • Development and testing — simpler setup, no extra hardware
  • Non-critical doors — interior doors where brief connectivity loss is tolerable

For production with Ethernet, the BLE time-division concern disappears for sync operations — Ethernet runs independently of the radio, so BLE remains available at all times. The WiFi/BLE switching described below only applies to WiFi-based deployments.

WiFi/BLE Time-Division

Normal operation (5 min cycle):
├── 0:00 - 4:55  BLE active (NUKI control available, NFC scanning active)
├── 4:55 - 5:00  WiFi active (sync allow-list, report events, heartbeat)
└── repeat

On CONDITIONAL card scan:
├── WiFi on (POST approval request)
├── Wait up to 60 seconds for response
├── WiFi off, BLE on
└── If approved: unlock NUKI via BLE

Allow-List Validity

  • Monotonic version: ESP32 rejects lists with version <= current_version
  • Validity window: valid_until is generated_at + 24 hours by default
  • Staleness handling: If valid_until has passed and no sync is possible, UNRESTRICTED and EMERGENCY entries still work. SCHEDULED entries are denied (cannot reliably evaluate time rules with potentially incorrect local time).

Consequences

  • Card scan to door unlock is fast (~50ms: Wiegand decode + UID lookup + BLE command)
  • Key revocation takes effect within 5 minutes (next sync)
  • ESP32 needs ~10KB of flash for the allow-list (sufficient for 200 entries)
  • LittleFS partition (192KB) stores the allow-list file persistently
  • The backend must be reachable at least once every 24 hours for SCHEDULED entries to remain valid
  • CONDITIONAL access requires WiFi at the moment of card scan