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 backend exposes a single WebSocket endpoint at /ws. When a Raspberry Pi node submits new BLE detections, the server broadcasts a detection_event message to all connected WebSocket clients. The React frontend uses this feed to refresh the dashboard in real time without polling. The WebSocket is managed by a global WebSocketManager instance (ws_manager in src/websocket/manager.py). The current count of open connections is visible in the GET /health response under the websocket_connections field.

Connection URL

ws://<host>:<port>/ws
On a local development server:
ws://localhost:8000/ws
For TLS-protected deployments, use the wss:// scheme.

Message types

The server sends JSON messages. Each message has a type field that identifies the event kind.

detection_event

Broadcast to all clients whenever a bulk or single detection submission is successfully processed.
{
  "type": "detection_event",
  "timestamp": "2026-05-15T10:30:00.123456+02:00",
  "device_id": "pi-entrance-01",
  "count": 3
}
FieldTypeDescription
typestringAlways "detection_event"
timestampstringISO 8601 server time when the event was emitted
device_idstringID of the Raspberry Pi that submitted the detections
countnumberNumber of detection records that were saved in this batch

stats_update

Broadcast when aggregated statistics are updated. The data object contains the updated stats payload.
{
  "type": "stats_update",
  "timestamp": "2026-05-15T10:30:05.000000+02:00",
  "data": {}
}
FieldTypeDescription
typestringAlways "stats_update"
timestampstringISO 8601 server time when the event was emitted
dataobjectUpdated statistics payload

Ping/pong keepalive

The server echoes the string "pong" whenever it receives the text "ping". Use this to detect stale connections and prevent proxies from closing idle WebSocket sessions. The frontend sends a ping every 15 seconds:
// Send keepalive
socket.send('ping');

// Server responds with the plain text string "pong"
Only the exact string "ping" triggers a "pong" response. All other plain-text messages are ignored. JSON messages from the server always have a type field and are never "pong".

Health check integration

The GET /health endpoint reports how many WebSocket clients are currently connected:
curl http://localhost:8000/health
{
  "status": "healthy",
  "timestamp": "2026-05-15T10:30:00+02:00",
  "websocket_connections": 2
}

Browser JavaScript example

The following pattern mirrors the connection logic used by the React frontend in frontend-web/src/services/websocket.js.
const WS_URL = 'ws://localhost:8000/ws';

class WebSocketService {
  constructor() {
    this.socket = null;
    this.listeners = new Map();
    this.connected = false;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000; // ms, doubled on each attempt
    this.pingInterval = null;
  }

  connect() {
    if (this.socket?.readyState === WebSocket.OPEN) return;

    this.socket = new WebSocket(WS_URL);

    this.socket.onopen = () => {
      this.connected = true;
      this.reconnectAttempts = 0;
      this.startPing();
    };

    this.socket.onclose = (event) => {
      this.connected = false;
      this.stopPing();
      // Reconnect on unexpected close (not a clean 1000 close)
      if (event.code !== 1000) this.scheduleReconnect();
    };

    this.socket.onmessage = (event) => {
      // Ignore plain-text keepalive responses
      if (!event.data.startsWith('{')) return;
      const message = JSON.parse(event.data);
      // Dispatch to registered listeners by message type
      if (message.type && this.listeners.has(message.type)) {
        this.listeners.get(message.type).forEach(cb => cb(message));
      }
    };
  }

  on(eventType, callback) {
    if (!this.listeners.has(eventType)) this.listeners.set(eventType, []);
    this.listeners.get(eventType).push(callback);
  }

  startPing() {
    this.pingInterval = setInterval(() => {
      if (this.socket?.readyState === WebSocket.OPEN) {
        this.socket.send('ping');
      }
    }, 15000);
  }

  stopPing() {
    clearInterval(this.pingInterval);
    this.pingInterval = null;
  }

  scheduleReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
    this.reconnectAttempts++;
    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
    setTimeout(() => this.connect(), delay);
  }

  disconnect() {
    this.stopPing();
    this.socket?.close(1000, 'Client disconnect');
    this.socket = null;
    this.connected = false;
  }
}

// Usage
const ws = new WebSocketService();
ws.connect();

ws.on('detection_event', (message) => {
  console.log(`New detections from ${message.device_id}: ${message.count} records`);
  // Trigger a dashboard refresh here
});

ws.on('stats_update', (message) => {
  console.log('Stats updated:', message.data);
});
Reconnection uses exponential back-off starting at 1 second, doubling on each attempt up to 5 retries. After 5 failed attempts the service stops retrying and emits a connection_error event so the UI can display a persistent error state.

Build docs developers (and LLMs) love