BLETrack
BLETrack is a FastAPI service for tracking BLE presence data from ESPresense through MQTT, storing it in TimescaleDB, and exposing a simple web dashboard for enrollment and device mapping.
The data flow is:
- ESPresense publishes device observations and fingerprint topics to MQTT.
- A background MQTT ingester in the API subscribes to
espresense/#. - Presence events are written to
presence_events(Timescale hypertable). - Fingerprint topics are written to
fingerprint_registry. - The FastAPI app exposes endpoints for health, enrollment, presence queries, and managed device mappings.
index.html(served by FastAPI) calls those endpoints for live monitoring and admin actions.
Core components:
main.py: FastAPI app and HTTP routes.mqtt_ingester.py: MQTT subscriber and DB writes.db.py: schema setup and SQL queries.espresense_wrapper.py: calls ESPresense enrollment APIs.index.html: lightweight dashboard UI.
- Python 3 + FastAPI
- PostgreSQL + TimescaleDB
- Mosquitto MQTT broker
- ESPresense nodes (ESP32)
- Plain HTML/JS dashboard (served at
/)
- Create local env file:
cp .env.example .env- Start infrastructure:
docker compose up -d- Run the API:
python main.py- Open the dashboard:
http://localhost:5055/
- Presence stream:
espresense/devices/+/+ - Fingerprint registry:
espresense/settings/fingerprints/+
Notes:
- Presence payloads are stored as raw JSON in
presence_events.payload. - Fingerprint topics are used to determine paired/enrolled identities.
timescaledb-init.sql initializes the main time-series table (presence_events) and indexes.
App-managed tables:
fingerprint_registry: latest fingerprint value by fingerprint device ID.managed_devices: user-defined mapping between observed IDs and friendly names/types.
Retention:
RETENTION_DAYScontrols data retention policy forpresence_events(default:90).
GET /health- Service, DB, and MQTT ingester status.
GET /enroll/start?device_type=phone&name=my_device- Starts ESPresense enrollment mode.
GET /enroll/cancel- Cancels enrollment mode.
POST /espresense/filter- Updates ESPresense include/exclude filters via
/wifi/extrasform submit. - JSON body:
{"include": "phone:andrew_iphone", "exclude": "enroll_mode"}
- Updates ESPresense include/exclude filters via
POST /pair/finalize- Finalizes a device pairing from fingerprint ID, upserts
managed_devices, and auto-rebuilds ESPresense include filter from active paired devices. - JSON body:
{"public_name": "Andrew iPhone", "device_type": "phone", "fingerprint_device_id": "phone:andrew_iphone"}
- Finalizes a device pairing from fingerprint ID, upserts
POST /pair/remove- Marks a paired device inactive and auto-rebuilds ESPresense include filter.
- JSON body:
{"public_name": "Andrew iPhone"}
GET /dashboard/current?limit=500- Returns only active paired devices with current online/offline status and latest signal snapshot.
GET /presence/latest?limit=100&room=sce&paired_only=true- Latest event per
device_id.
- Latest event per
GET /presence/history?minutes=60&limit=1000&room=sce&paired_only=true- Presence history window.
GET /debug/fingerprints?limit=200- Fingerprint registry rows.
GET /devices/discovered?limit=500- Latest discovered raw device IDs.
GET /devices/managed?limit=500- Managed mappings with last-seen join.
POST /devices/managed- Upserts managed mapping (
display_name,observed_device_id,device_type,fingerprint_device_id,is_active).
- Upserts managed mapping (
Common environment variables:
DATABASE_URLMQTT_BROKER(default:localhost)MQTT_PORT(default:1883)MQTT_TOPIC(default:espresense/#)ESPRESENSE_BASE_URLESPRESENSE_TIMEOUT_SECONDS(default:5.0)RETENTION_DAYS(default:90)
See .env.example for the full template.