Skip to content

edg2411/sentinel

Repository files navigation

Sentinel UWB Tracking System

Sentinel is an indoor positioning and geofencing system built with an ESP32, Node-RED, and Python. It simulates UWB (Ultra-Wideband) Time-of-Flight distances, calculates 2D positions via trilateration, and visualizes the hardware in a web dashboard with real-time restricted-zone alerting.

Architecture Overview

The system is broken down into four main micro-components:

  1. The Edge Device (ESP32): Generates (simulates) distances to 3 fixed anchors. Instead of heavy processing, it sends a highly efficient, packed binary payload (C-struct) over TCP. It also listens for ALERT or SAFE replies to toggle an onboard hardware LED.
  2. The Gateway Traffic Controller (Node-RED): Unpacks the raw bytes from the ESP32 into a clean JSON object. It forwards this data to the math engine, receives the calculated (X,Y) coordinates, determines if the tag has breached the geofenced area, and updates both the ESP32 and the Web UI.
  3. The Math Engine (Python microservice): A Flask API running scipy.optimize. It takes the 3 raw anchor distances and uses the Least-Squares trilateration algorithm to pinpoint the exact X,Y coordinates of the tag.
  4. The Web Dashboard (React): A standalone frontend under web/ for the new operator dashboard. The older Node-RED dashboard flow still exists under gateway/nodered/.

Project Structure

sentinel/
├── README.md                      <-- This documentation
├── docker-compose.yml             <-- Main container stack (project name: gateway)
│
├── device/                        <-- ESP32 Edge Code (ESP-IDF C)
│   ├── CMakeLists.txt             <-- Project build config
│   ├── sdkconfig.defaults
│   └── main/
│       ├── CMakeLists.txt         <-- Component requirements (includes math, gpio, etc.)
│       └── tcp_client.c           <-- Main firmware: Simulates movement, packs binary, handles LED
│
├── gateway/                       <-- Server-side Services
│   ├── install.sh                  <-- Gateway bootstrap and Node-RED flow provisioning
│   ├── nodered/
│   │   ├── tcp_flow_exported.json <-- The full Node-RED flow (TCP, Geofencing, Dashboard)
│   │   └── ui/
│   │       └── map_template.html  <-- The Vue.js Dashboard 2.0 Map component code
│   │
│   ├── logs_api/                  <-- Flask API for persisted log data
│   └── trilateration/             <-- Python Math Microservice
│       ├── app.py                 <-- Flask API doing Scipy Trilateration
│       └── requirements.txt       <-- Python dependencies (flask, scipy, numpy)
└── web/                           <-- Standalone React frontend dashboard
    ├── package.json
    ├── simulation/                 <-- Scripts that generate live-positioning data for testing
    ├── public/live/                <-- Runtime live-positioning JSON feed location
    └── src/

How to Run the System

The main runtime path is Docker Compose. The root docker-compose.yml starts Node-RED, the Python Math Engine, the logs API, PostgreSQL, and the React dashboard.

1. First-Time Gateway Provisioning

Run the gateway installer once to configure USB permissions, seed the Node-RED flow, and start the Docker stack:

cd gateway
sudo ./install.sh

What this script does:

  1. Sets up persistent USB permissions (udev rules) for the ESP32 serial connection so you don't need to chmod.
  2. Creates gateway/data and pre-seeds package.json with the required Node-RED plugins (@flowfuse/node-red-dashboard and node-red-node-serialport).
  3. Imports gateway/nodered/serial_flow_exported.json as the default Node-RED flow.
  4. Starts the main Docker Compose stack from the repository root docker-compose.yml.
  5. Forces Node-RED plugin installation inside the container.

2. Daily Docker Startup

After the first provisioning run, start the full system from the repository root:

docker compose up -d

To stop the stack:

docker compose down

Once the stack finishes initializing (takes ~30 seconds), Node-RED will be available at http://localhost:1880, the legacy Node-RED dashboard at http://localhost:1880/dashboard, and the React dashboard at http://localhost:5173.

3. Flash the ESP32

Boot up the ESP-IDF environment, compile the C code, and flash it to your ESP32.

cd device
# Make sure your ESP-IDF export script is sourced first!
idf.py build flash monitor

Note: Make sure to update the HOST_IP_ADDR in tcp_client.c if your Node-RED server is running on a different IP address.

Optional Native Development

The Docker stack already runs these services. Only run them natively when testing a service outside Docker.

Run the Python Math Engine natively:

cd gateway/trilateration
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python app.py

Run the React dashboard natively:

cd web
npm install
npm run dev

React Dashboard Live-Data Simulation

The React dashboard is written against a generic live-positioning feed, not against simulation-specific code. In production, the same data shape can be written by the gateway/device pipeline. For local testing, web/simulation/entry-zone-simulation.js acts as a temporary data producer.

The dashboard polls:

web/public/live/positioning.json

That JSON contains the current anchors, tags, danger-zone polygon, and tag status. The React app does not know whether the file was produced by the simulation script or by a future device integration.

Start the web dashboard stack:

docker compose up -d web

Run the entry-zone simulation inside the Docker web container:

docker compose exec -w /app web npm run simulate:entry-zone

While this command is running, it continuously updates public/live/positioning.json with a tag moving into the four-anchor danger zone near entries 14-15 / 16-17. Open the React dashboard at:

http://localhost:5173

When the tag enters the restricted zone, the dashboard posts the alert to the logs API, which stores it in PostgreSQL. Logs are visible at:

http://localhost:5173/logs

Stop the running simulation command with Ctrl+C. To remove the generated live feed and return the dashboard to its default fallback view:

docker compose exec -w /app web npm run simulate:clear

The same commands can be run natively from web/:

npm run simulate:entry-zone
npm run simulate:clear

How it Works (The Real-Time Loop)

  1. ESP32 calculates a perfect mathematical circle path, turning that path into "simulated distances" from the anchors.
  2. ESP32 sends a 22-byte raw binary packet to Node-RED via TCP port 1234.
  3. Node-RED converts the bytes to JSON and sends them via HTTP POST to http://localhost:5000/calculate.
  4. Python replies with the exact {"x": ..., "y": ...}.
  5. Node-RED checks if the X,Y coordinates fall inside the 10x10 restricted central zone (Geofence).
  6. Node-RED pushes the coordinates/alert status to the Vue Canvas to redraw the Map.
  7. Node-RED replies to the ESP32 TCP socket with either "ALERT\n" or "SAFE\n".
  8. ESP32 parses the reply and toggles the blue LED on GPIO 2.

ESP-NOW Extension Project

The device/test directory contains implementations for an alternative communication layer using ESP-NOW to combat Wi-Fi congestion.

  1. esp_now_receiver: A transparent USB-Serial bridge. It listens for 22-byte UWB payloads over the ESP-NOW protocol and blindly redirects the binary structs directly to the hardware UART (/dev/ttyUSB0), bypassing standard strings and potential line-feed misalignments entirely.
  2. esp_now_sender: It replicates the exact tcp_client.c 22-byte layout but pushes it via esp_now_send to the hardcoded MAC address of the receiver. It implements dynamic coordinate sweeping (X/Y geofence bounds simulation) to thoroughly test the Node-RED trilateration and data validation scripts. By mocking movement across 12-meter bounds at a 10Hz transmission frequency, we map payload stability directly to the Gateway without the need for active RF anchors.

Connecting ESP-NOW via Docker

The primary flow uses TCP inside Node-RED. However, the ESP-NOW serial implementation is built to hot-plug automatically via gateway/install.sh:

  1. The container uses privileged: true and maps /dev:/dev, so any /dev/ttyUSB0 device is automatically hot-plugged inside Docker when connected!
  2. The udev rules automatically make the port writeable (chmod 666 happens natively via systemd).
  3. The serial_flow_exported.json flow is auto-imported, and includes the highly robust Sliding Window Buffer algorithm necessary to cleanly slice and extract binary UWB payloads from mixed UART streams out of the ROM Bootloader chunk.

(Note for WSL Users: Use usbipd-win like usbipd attach --wsl to pass the device from Windows to WSL, and Node-RED will pick it up instantly).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors