Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AngelAmoSanchez/TFG-RaspberryPi-BLE/llms.txt

Use this file to discover all available pages before exploring further.

The system is divided into three independent layers. The Raspberry Pi agent collects and anonymizes raw BLE data; the cloud backend stores, aggregates, and re-broadcasts it; and the web dashboard renders live statistics and exports. Each layer communicates over a single, well-defined interface, so any component can be swapped or scaled without touching the others.

Layer 1 — Raspberry Pi agent

The agent (raspberry-pi/src/main.py) runs as an asyncio loop on the Pi. On every scan cycle it:
  1. Calls BLEScanner.scan_devices() via the Bleak library to collect raw BLE advertisements.
  2. Passes each Detection (MAC address + RSSI + timestamp) through DetectionProcessor.
  3. Anonymizes the MAC address with SHA-256 (hashlib.sha256), producing a 64-character hex digest stored as device_hash.
  4. Classifies the device into a proximity zone based on RSSI.
  5. Serializes the resulting Device objects and sends them to the cloud backend via MQTT or HTTP.
  6. Waits for SCAN_INTERVAL seconds (default: 30 s) and repeats.
An in-memory buffer retains up to MQTT_BUFFER_SIZE (default: 100) messages when the backend is unreachable, and flushes them automatically on reconnection.

Zone classification

Zone boundaries are defined in raspberry-pi/src/scanner/detection.py and are configurable via environment variables.
class Zone(Enum):
    """Zonas de proximidad según la señal RSSI"""

    NEAR = "near"    # RSSI >= -60 dBm
    MEDIUM = "medium" # -75 <= RSSI < -60 dBm
    FAR = "far"      # RSSI < -75 dBm

    def get_description(self) -> str:
        """Devuelve descripción legible de cada zona"""
        descriptions = {
            Zone.NEAR: "Zona Cercana (0-2m)",
            Zone.MEDIUM: "Zona Media (2-5m)",
            Zone.FAR: "Zona Lejana (>5m)",
        }
        return descriptions[self]
ZoneRSSI rangeEstimated distance
NEAR≥ −60 dBm0 – 2 m
MEDIUM≥ −75 dBm and < −60 dBm2 – 5 m
FAR< −75 dBm> 5 m
The thresholds NEAR_THRESHOLD (default -60) and MEDIUM_THRESHOLD (default -75) are tunable via .env on both the Pi agent and the cloud backend.

MAC anonymization

def anonymize_mac(self, mac_address: str) -> str:
    """Anonimiza dirección MAC usando SHA-256"""
    normalized_mac = mac_address.replace(":", "").replace("-", "").upper()

    hash_object = hashlib.sha256(normalized_mac.encode())
    return hash_object.hexdigest()
The MAC is normalized (colons and dashes stripped, uppercased) before hashing. The 64-character hex digest is the only identifier that ever leaves the device.

Layer 2 — cloud backend

The backend (backend-cloud/src/api/main.py) is a FastAPI application backed by PostgreSQL. On startup it:
  • Runs init_db() to create or migrate the schema via asyncpg.
  • Optionally starts an MQTT subscriber (start_mqtt_subscriber()) when MQTT_ENABLED=true.
  • Exposes REST routes under /api/v1/ for detections, statistics, devices, export, and settings.
  • Runs a WebSocket endpoint at /ws through a connection manager (ws_manager) that broadcasts new detection batches to all connected clients in real time.
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket endpoint para actualizaciones en tiempo real"""
    await ws_manager.connect(websocket)

    try:
        while True:
            data = await websocket.receive_text()
            if data == "ping":
                await websocket.send_text("pong")
    except WebSocketDisconnect:
        await ws_manager.disconnect(websocket)
A health check at GET /health reports the number of active WebSocket connections alongside a timestamp.

Layer 3 — web dashboard

The React frontend connects to ws://backend/ws on load and re-renders zone charts whenever the WebSocket delivers a new detection batch. Key capabilities:
  • Real-time zone counts — Recharts bar and line charts update without page refresh.
  • Hourly and daily trends — aggregated from the /api/v1/statistics endpoint.
  • CSV export — filtered by date range, zone, or device hash via the /api/v1/export endpoint.

Data flow

┌──────────────────────────────────────────┐
│          Raspberry Pi agent              │
│                                          │
│  BLE radio → Bleak scan                  │
│     ↓                                    │
│  Detection(mac, rssi, timestamp)         │
│     ↓                                    │
│  anonymize_mac() → device_hash (SHA-256) │
│     ↓                                    │
│  classify_zone() → NEAR / MEDIUM / FAR   │
│     ↓                                    │
│  Device(device_hash, rssi, zone, ts)     │
│     ↓                                    │
│  MQTT publish  ──OR──  HTTP POST         │
└──────────────────────┬───────────────────┘

          ┌────────────▼──────────────────┐
          │       Cloud backend           │
          │                               │
          │  MQTT subscriber ─┐           │
          │  HTTP endpoint  ──┼→ PostgreSQL│
          │                   │           │
          │  WebSocket push ←─┘           │
          └────────────┬──────────────────┘

          ┌────────────▼──────────────────┐
          │        Web dashboard          │
          │                               │
          │  WebSocket → Recharts charts  │
          │  REST calls → CSV export      │
          └───────────────────────────────┘

Communication modes

The Pi agent supports two transport modes selected by the COMMUNICATION_MODE environment variable.
MQTT is the recommended mode for production. The agent connects to a broker (for example, EMQX Cloud) and publishes detection batches to a configurable topic. The cloud backend subscribes to the same topic.Agent-side flow:
  1. MQTTClient connects to MQTT_BROKER:MQTT_PORT with optional TLS (port 8883).
  2. Each scan cycle, publish_detections() serializes the Device list to JSON and publishes to MQTT_TOPIC.
  3. If the publish fails, the message is queued in an in-memory buffer (max MQTT_BUFFER_SIZE messages).
  4. On reconnection, buffered messages are flushed in order.
Backend-side flow:
  1. When MQTT_ENABLED=true, start_mqtt_subscriber() subscribes to MQTT_TOPIC at startup.
  2. Each incoming message is parsed, persisted to PostgreSQL, and broadcast to WebSocket clients.
# Pi .env — MQTT mode
COMMUNICATION_MODE=mqtt
MQTT_BROKER=your-broker.eu-central-1.emqxsl.com
MQTT_PORT=8883
MQTT_TOPIC=tfg/detections/raw
MQTT_USERNAME=your_username
MQTT_PASSWORD=your_password
MQTT_BUFFER_SIZE=100
# docker-compose.yml — enable MQTT on the backend
environment:
  MQTT_ENABLED: "true"
  MQTT_BROKER: your-broker.eu-central-1.emqxsl.com
  MQTT_TOPIC: tfg/detections/raw

Component summary

ComponentRuntimeKey dependenciesTransport
Pi agentPython 3 + asyncioBleak, paho-mqttMQTT or HTTP
Cloud backendPython 3 + FastAPIasyncpg, PostgreSQL, aiomqttWebSocket push
Web dashboardReactRecharts, WebSocket APIWebSocket + REST

Build docs developers (and LLMs) love