Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ESP-32-RFID-WebUI

A small RFID access control project built around an ESP32 with a SSD1306 display and a lightweight Python server + dashboard.

  • When connected it syncs with the server using ETag/If-None-Match semantics for minimum filesystem and network strain.
  • Stores a compact authorization bitset on the device.
  • Capable of authorization during network interruption
  • Seamless reconnection.

Doxygen - Documentation


At a glance

  • ESP32 firmware (PlatformIO) which reads RFID scans, enforces authorization, and syncs a compact bitset representation of allowed card IDs.

  • A tiny Flask-based server and dashboard (lib/server.py, lib/dashboard.html) to manage cards and provide the sync API.

  • Persistence:

    • Bitset snapshot: LittleFS file /bits.bin
    • Allow/deny hash lists: LittleFS file /allow_deny.bin
    • Small metadata (ETag, max_id): NVS (Preferences)

Hardware

  • RFC552
  • SSD1306/SSD1315
  • Nodemcu-32s / esp-wroom

Features

  • Compact on-device authorization bitset (per-card_id bits) for fast local checks.
  • Offline allow/deny caches (64-bit FNV-1a hashes) persisted to LittleFS.
  • Server-first lookups when online, with fallback to offline caches. Quite easily reversed to be the other way around.
  • Efficient sync: server provides ETag for the bitset and /api/sync/meta for cheap polling.
  • Simple web UI to list/add/remove/toggle cards and to show last scanned UID.
  • Enrollment mode from dashboard: -Revoke on scan -Grant on scan
Screenshot 2025-11-29 173940

Repository layout

  • src/ — ESP32 firmware (PlatformIO project)
  • lib/ — Python server and dashboard
    • lib/server.py — Flask app
    • lib/dashboard.html — web UI
  • data/ — runtime data and example config
    • cards.db - local — SQLite DB used by the server autogenerated on first run
    • config.json - local - server config (rename example and upload to littleFS) / uncomment one time write in main as fallback
  • .pio_venv/ - local — local Python venv created by PlatformIO (not checked in)
  • platformio.ini — PlatformIO build configuration

API overview (used by device and dashboard)

  • GET /api/cards — list cards
  • POST /api/cards — add/update a card (body { "uid": "04A1...", "authorized": true })
  • GET /api/cards/<uid> — get a card
  • DELETE /api/cards/<uid> — soft-delete a card
  • PATCH /api/cards/<uid> — update authorized
  • POST /api/last_scan — device posts scanned UID (body { "uid": "..." })
  • POST /api/enroll — set enrollment mode { "mode": "grant" | "revoke" | null }
  • GET /api/status — returns { last_scanned, enroll_mode } (dashboard polls this)
  • GET /api/sync — full bitset payload { max_id, bits } (bits as hex); returns ETag header and supports If-None-Match
  • GET /api/sync/meta — lightweight { max_id, etag, bits_len } for cheap polling

TODO

  • Configurable autoremoval after period
  • Sleep mode and power-profile
  • Ongoing transition to ESP-IDF functions where performance gain outweighs loss of simplicity

Quick start

Prerequisites

  • PlatformIO (VSCode extension or CLI) for building/flashing the ESP32 firmware.
  • Python 3.8+ to run the Flask server locally (prefer using the project's .pio_venv).

Build & flash ESP32 firmware (PowerShell)

# Build (adjust environment name if needed)
-pio run 

Run the server locally

```powershell
# Install server deps into the project's venv (if not present)
& .\.pio_venv\Scripts\pip.exe install Flask flask-cors

# Run the server using the venv Python
& .\.pio_venv\Scripts\python.exe -u lib\server.py

The server listens on port 5000 by default. Open http://<server-ip>:5000/ to view the dashboard.


Conditional GET example (PowerShell + curl)

# Get meta first (recommended)
$response = Invoke-RestMethod -Uri "http://127.0.0.1:5000/api/sync/meta" -Method GET
$etag = $response.etag

# Request full sync only if changed
$code = & curl.exe -s -o sync.json -w "%{http_code}" -H "If-None-Match: $etag" "http://127.0.0.1:5000/api/sync"
if ($code -eq '304') { Write-Host 'No change' } else { Write-Host 'Updated, saved sync.json' }

Persistence details

  • Bitset snapshot: written to LittleFS /bits.bin and max_id stored in NVS so the device can reconstruct exact size on boot.
  • Allow/deny: a compact binary file /allow_deny.bin is stored on LittleFS with counts and raw uint64_t hashes.
  • ETag: SHA1 hex of the bitset returned by the server; stored in NVS under key bitset_etag and used in If-None-Match header.

Troubleshooting

  • ModuleNotFoundError: No module named 'flask' — ensure you run the server with the project's venv Python or install Flask in the interpreter you use.

  • Dashboard not showing changes: hard reload the browser (Ctrl+F5) or open an incognito tab. The server serves the dashboard with Cache-Control: no-store but some caches can still be sticky.

  • LittleFS wear: the firmware currently writes allow/deny and bitset files on changes; if scans are extremely frequent you may want to debounce writes.


Development notes

  • Server log/debug prints have been added to surface last_scanned updates and status polls — check the terminal running lib/server.py to confirm receipt of scan POSTs.
  • If you want the server to return compact hash metadata or change the sync payload format, update lib/server.py and coordinate with the device code in src/AuthSync.cpp.
  • (Ensure Python and C/C++ hashing implementations are the same if you change hash format.)

Demonstration

VID_20251129_174131.5.mp4
VID_20251129_174131.6.mp4

Need to fix updating entrollment status symbol (GR) if DB lost during grant enrollment

Example Serial Monitor

Screenshot 2025-11-30 021359 Screenshot 2025-11-30 021343

Example Logic Flow

Ljus Rfid Chart

About

ESP-32 RFID project with SD1316 LED display and SQLite3 database + WebUI. Inlämningsuppgift IOT25S, Rasmus Tikkanen.

Resources

Stars

Watchers

Forks

Releases

Contributors

Languages