A real-time drone telemetry monitor written in Rust. Simulated drones send JSON telemetry over TCP to a multi-threaded processing pipeline that detects anomalies (altitude drops, speed spikes, battery warnings, signal loss) and displays everything live in a terminal dashboard.
An async Tokio TCP listener accepts connections from any number of drone simulators. Each connection is handled in its own spawned task. Incoming JSON lines are deserialized into TelemetryPacket structs and routed to worker threads via partitioned mpsc channels — drone_id % num_workers selects the channel, so all packets for a given drone always go to the same worker.
Each drone runs as an independent async Tokio task. It generates realistic telemetry (altitude drift, GPS walk, gradual battery drain) and injects low-probability anomalies — a ~2.5% chance per tick of a sudden altitude drop or speed spike. Drones automatically reconnect if the TCP connection is lost.
Worker threads consume from their assigned channel and evaluate each packet against a rolling window of recent readings:
| Anomaly | Trigger condition |
|---|---|
| Altitude Drop | Current altitude drops >30 m below rolling average (window=10) |
| Speed Spike | Current speed exceeds rolling average by >15 m/s |
| Battery Warning | Battery falls at or below 20% (fires once per crossing) |
| Battery Critical | Battery falls at or below 10% (fires once per crossing) |
| Signal Lost | No packet received from an online drone for >5 seconds |
| Signal Restored | First packet received after a Signal Lost event |
All threads share a single Arc<Mutex<DashboardState>>. The state tracks per-drone status (last packet, altitude history ring buffer, alert count, connection status) and a global alert queue capped at 200 entries. The update_drone method returns a boolean indicating whether the drone was previously signal-lost, allowing workers to emit a restoration alert without holding the lock across the check.
An async task wakes every second and scans the drone map for any Online drone whose last_seen Instant exceeds the configured threshold. It uses a two-phase lock pattern — collect IDs without the lock held across I/O, then re-acquire to mutate — to avoid holding the mutex while iterating.
Alert messages are forwarded to a background thread via a bounded SyncSender<String> (capacity 512). The thread appends each message to alerts.log. Using a sync channel decouples the hot path from disk I/O and provides natural backpressure.
The main thread runs a 150 ms tick loop rendering four panels:
- Header — uptime, total packets processed, drone count
- Fleet Status Table — per-drone: ID, connection status, altitude, speed, battery (color-coded), GPS, packet count, alert count
- Alert Feed — last 9 alerts in reverse-chronological order, color-coded by type
- Altitude Sparklines — one mini bar chart per drone (up to 8) showing altitude history using Unicode block characters
Requirements: Rust 1.75+ and Cargo (install via rustup)
git clone <repo-url>
cd drone-telemetry
cargo build --releaseDefault (5 drones, 3 workers, config from config.toml):
cargo runOverride drone and worker count on the command line:
cargo run -- -n 10 -w 4Use a custom config file:
cargo run -- --config my_config.tomlSee all options:
cargo run -- --helpExpected output (header bar):
DRONE TELEMETRY MONITOR │ Uptime: 00:00:03 │ Packets: 47 │ Drones: 5 │ Press Q to quit
Inside the dashboard the fleet table populates within ~1 second. Anomaly alerts appear in the alert feed within the first minute. Press Q, Esc, or Ctrl+C to quit.
[server]
address = "127.0.0.1"
port = 7878
[simulation]
num_drones = 5 # number of simulated drones
num_workers = 3 # number of anomaly-detection worker threads
send_interval_ms = 500
[detection]
altitude_drop_threshold = 30.0 # meters below rolling average
speed_spike_threshold = 15.0 # m/s above rolling average
battery_warning_threshold = 20.0 # percent
battery_critical_threshold = 10.0
signal_lost_seconds = 5
rolling_window = 10 # packets used for rolling averages
[logging]
alert_log_file = "alerts.log" # set to "" or remove to disable- Concurrency —
std::threadworker pool + Tokio async runtime running simultaneously;Arc<Mutex<T>>for shared mutable state across all threads/tasks - Smart pointers —
Arcfor shared ownership across threads;Mutexfor interior mutability - Async / await — TCP server and all drone simulators are async Tokio tasks
- Channels —
mpsc::SyncSender/mpsc::Receiverfor both the worker pipeline and the file logger - Generics & traits —
serde::{Serialize, Deserialize}derive macros;anyhow::Result<T>for ergonomic error propagation throughout - Ownership & lifetimes — two-phase lock pattern in the watchdog avoids holding
MutexGuardacross await points;VecDequering buffer for bounded altitude history
With default settings (5 drones, 500 ms send interval):
| Metric | Observed value |
|---|---|
| Packets/sec (5 drones) | ~10 |
| Packets/sec (20 drones) | ~40 |
| Alert detection latency | < 1 tick (150 ms) |
| Signal-lost detection latency | ~1 s (watchdog) |
| Memory usage (5 drones, 60 s) | ~6 MB RSS |
Anomaly injection rates (~2.5% per tick per drone) produce roughly 1–3 altitude/speed alerts per drone per minute at the default send interval, which keeps the alert feed active without flooding it.