╱╲ ╱╲
╱ ╲ ░░░░░░░ ╱ ╲
╱ ◉ ╲▄▄▄▄▄▄▄╱ ◉ ╲
╱╲ ╱╲▌▐▌▐▌▐╲ ╱╲
╱ ╲ ╱ ╲ ─── ╱ ╲ ╱ ╲
╲╱ ▼ ╲╱
╲▶
Autonomous Drone Traffic Management — operator console for the urban airspace
Demo • Features • Quick Start • Architecture • Conflict Detection • Configuration • Development • Tech Stack
Live operator console — eight simulated drones cross-cross the San Francisco bay airspace, the look-ahead conflict detector fires on a near-miss pair, and the planner authorizes a new flight against three active no-fly zones.
| Feature | Description |
|---|---|
| Live airspace map | Dark CARTO basemap with multi-drone markers, breadcrumb trails, weather cells, vertiports and no-fly polygons |
| Look-ahead conflict detector | 30-second linear-extrapolation pairwise check at 2 Hz; emits tiered (advisory/warning/critical) alerts with TTC and minimum 3-D separation |
| Flight authorization pipeline | Submitted plans run through no-fly + weather + vertiport-slot checks and produce a FlightAuthorization (approved / conditional / rejected) with auditable reasons |
| Multi-drone WebSocket fan-in / fan-out | Each simulated drone holds its own WS to /ws/ingest/{id}; the operator console subscribes to a multiplexed /ws/airspace channel carrying telemetry, alerts, plan updates, weather and zone changes |
| Bidirectional command channel | Operator can GROUND, RELEASE, HOLD or REROUTE any drone; commands tunnel through the WS, the simulator reacts and the DB-side status updates |
| Synthetic weather cells | Wind, visibility and ceiling oscillate; the planner consults them when authorizing a plan |
| Standalone simulator | Separate Docker service rotates through nominal, near_miss_pair and no_fly_breach scenarios so the demo always has something to look at |
| One-command setup | docker compose up --build brings the full backend + simulator + frontend stack online |
- Docker Desktop or Docker Engine + Compose v2.
git clone https://github.com/expertdicer/skywarden.git
cd skywarden
cp .env.example .env # tweak conflict thresholds if you like
docker compose up --buildOpen http://localhost:3000 — the operator
console will populate with the seeded fleet within ~10 seconds. The
simulator rotates scenarios every two minutes; alerts fire during
near_miss_pair.
┌──────────────────────┐ ┌────────────────────────────────┐ ┌──────────────────────┐
│ frontend │ │ backend │ ws │ simulator │
│ Next.js 14 + TS │◄─ws────►│ FastAPI + Python 3.12 │◄────────┤ N drone tasks │
│ Leaflet + recharts │ multi- │ SQLModel + SQLite │ /ws/ │ /ws/ingest/{id} │
│ Zustand + Framer │ plexed │ Conflict detector @ 2 Hz │ ingest │ scenarios.py │
│ REST for CRUD │ REST │ Flight planner / authorization│ │ │
└──────────────────────┘ ──────►└────────────────────────────────┘ └──────────────────────┘
:3000 :8000
Channel multiplexing
A single operator socket (/ws/airspace) carries tagged messages:
Operator commands (ground_drone / release / hold / reroute) travel
back along the same socket and are forwarded over /ws/ingest/{drone_id}
so the simulated drone reacts in real time.
For every drone with a recent telemetry snapshot the detector linearly
extrapolates a trajectory (lat, lon, alt) over a configurable look-ahead
horizon (default 30 s, sampled every 2 s). For each pair (a, b) the
service computes the minimum 3-D separation across the horizon. If the
minimum horizontal distance is below CONFLICT_MIN_HORIZONTAL_M and
the vertical separation is below CONFLICT_MIN_VERTICAL_M, a
ConflictAlert is created with severity tiered by time-to-conflict:
| Severity | Trigger |
|---|---|
critical |
TTC ≤ 8 s OR horizontal separation < 20 m |
warning |
TTC ≤ 18 s OR horizontal separation < 35 m |
advisory |
otherwise (still inside the protection volume) |
Alerts are deduplicated per pair with a configurable cool-down
(CONFLICT_COOLDOWN_S, default 20 s) so the feed stays informative rather
than spammy. Acknowledged alerts can be resolved via the UI.
| Variable | Default | Description |
|---|---|---|
DB_PATH |
/data/skywarden.db |
SQLite database path inside the backend container |
ALLOWED_ORIGINS |
http://localhost:3000 |
Comma-separated CORS origins |
CONFLICT_HORIZON_S |
30 |
Look-ahead horizon (s) for trajectory extrapolation |
CONFLICT_SAMPLE_DT_S |
2 |
Sample interval (s) along the predicted trajectory |
CONFLICT_MIN_HORIZONTAL_M |
50 |
Minimum horizontal separation (m) before flagging |
CONFLICT_MIN_VERTICAL_M |
15 |
Minimum vertical separation (m) before flagging |
CONFLICT_TICK_HZ |
2 |
Detector run frequency (Hz) |
BACKEND_WS_URL |
ws://backend:8000 |
Simulator WS endpoint |
BACKEND_HTTP_URL |
http://backend:8000 |
Simulator REST endpoint (fleet bootstrap) |
SIM_TICK_HZ |
4 |
Telemetry tick rate per simulated drone (Hz) |
SIM_DRONES |
8 |
Number of simulated drones |
cd backend
pip install uv
uv pip install --system -e ".[dev]"
DB_PATH=./skywarden.db uvicorn app.main:app --reload --port 8000cd frontend
npm install
cp .env.local.example .env.local
npm run devcd simulator
pip install -e .
BACKEND_HTTP_URL=http://localhost:8000 BACKEND_WS_URL=ws://localhost:8000 \
python -m simulator.maincd backend
pytest -vtest_conflict_detector.py covers the four canonical trajectory cases
(head-on, overtake, vertical-pass, no-conflict) plus the cooldown logic.
test_flight_planner.py covers no-fly violation, weather rejection and the
happy path. test_drones_api.py covers the REST CRUD via TestClient.
test_seed.py proves the bootstrap is idempotent.
| Layer | Technology |
|---|---|
| Frontend framework | Next.js 14 (App Router) |
| Language | TypeScript 5 |
| Map | Leaflet + react-leaflet on dark CARTO Positron tiles |
| Charts | Recharts 2 |
| Styling | Tailwind CSS 3 |
| State | Zustand 4 |
| Animation | Framer Motion 11 |
| Backend framework | FastAPI |
| Backend language | Python 3.12 |
| ORM / DB | SQLModel + SQLite |
| Real-time transport | WebSockets (multiplexed operator channel + per-drone ingest channels) |
| Container | Docker + Docker Compose v2 |
MIT © 2026 — see LICENSE for details.
{ "type": "telemetry", "drone_id": 3, "lat": …, "lon": …, "alt_m": …, "heading_deg": …, "speed_mps": …, "battery_pct": …, "status": "enroute" } { "type": "alert", "alert": { "drone_a_id": 3, "drone_b_id": 7, "min_horizontal_m": 28, "min_vertical_m": 4, "severity": "warning", … } } { "type": "plan_update", "plan": { … }, "authorization": { "decision": "conditional", "reasons": [ … ] } } { "type": "weather", "cells": [ … ] } { "type": "zone_change", "zone": { … } } { "type": "command_ack", "drone_id": 3, "action": "ground_drone", "accepted": true }