dronewatch

Passive multi-sensor drone detection — WiFi OUI + OpenDroneID RemoteID decoder

v0.1.1
NixOS x86_64

Quick Start

Install via jcli (recommended once shipped)

jcli install dronewatch

Decode a captured pcap (no root, no monitor mode)

dronewatch decode capture.pcap
# emits one NDJSON line per contact_update on stdout

Live capture on a monitor-mode interface

# put your WiFi card in monitor mode first
sudo iw dev wlan0 interface add wlan0mon type monitor
sudo ip link set wlan0mon up

# then run dronewatch — needs CAP_NET_RAW
sudo dronewatch run --wifi-iface wlan0mon --alert-min LOW
sudo dronewatch run --wifi-iface wlan0mon --output json --log-dir /var/log/dronewatch

What it does

dronewatch is a passive, listen-only drone detection CLI. It opens a monitor-mode WiFi interface (or replays an offline pcap), parses 802.11 management frames, and surfaces any drone signal it finds — either by drone-vendor OUI / drone-shaped SSID, or by the OpenDroneID broadcast that EU 2021/664 and FAA Part 89 mandate. Each detection is fused into a single Contact by a correlator keyed on MAC and UAS-ID, then written as a one-line alert (terminal / syslog-friendly) or NDJSON (machine-friendly).

Subcommands

CommandWhat it does
dronewatch runLive detection loop on a monitor-mode WiFi interface. --output = plain | json. Optional --log-dir for daily-rotated NDJSON.
dronewatch scanOne-shot timed capture; prints packets_seen / sensor_events / rule_hits summary and exits. Useful as a smoke-test.
dronewatch decode <pcap>Offline replay of a .pcap or .pcapng file through the same pipeline as run. No root required.
dronewatch list-hwEnumerates /sys/class/net; flags interfaces that look wireless-capable.
dronewatch versionPrints version + tagline.

Detection scope (v0.1)

v0.1 ships the highest-confidence detection paths first, deliberately. Other vectors arrive in later phases as the spec phases them out.

Sensorv0.1Notes
WiFi OUI / SSIDDJI / Parrot / Autel / Skydio / Yuneec OUIs + drone-named SSIDs.
OpenDroneID over WiFiPure-Rust decoder, ASTM F3411-22a Basic ID + Location + Operator ID.
Bluetooth LEv0.2Including OpenDroneID-over-BLE Long Range.
ADS-B Mode-Sv0.2Beast TCP from dump1090 for manned-aircraft deconfliction.
SDR FFT + FHSSv0.3SoapySDR + rule-based RF pattern classifier.
MAVLink, acousticv0.5Feature-gated; both optional at build time.
ONNX RF classifierv1.0ort-backed; falls back to rule engine if model file missing.

NDJSON event shape

{
  "ts": "2026-06-20T14:32:07.123Z",
  "event": "contact_update",
  "contact": {
    "id": "<uuid>",
    "first_seen": "2026-06-20T14:32:01.501Z",
    "last_seen":  "2026-06-20T14:32:07.123Z",
    "confidence": 0.87,
    "threat_level": "MEDIUM",
    "label": "DJI drone (WiFi)",
    "sources": ["wifi", "remoteid"],
    "mac": "60:60:1f:aa:bb:cc",
    "uas_id": "FIN1234567890",
    "position": { "lat": 49.6116, "lon": 6.1319 },
    "altitude_m": 78.5,
    "speed_mps":  12.3,
    "rssi_dbm":  -62,
    "raw_events": ["...","..."]
  }
}

Exit codes

CodeMeaning
0Ok
2Usage error (clap or bad --alert-min)
3Sensor / capture / integrity failure

Touches / Produces / Gates

Build from source

cd tools/dronewatch/rust
# libpcap-dev required for the wifi feature (default)
cargo build --release
cargo test                                  # 25 unit + 5 integration tests

Toolchain pin: Rust 1.85.0. Dependencies: clap, serde, serde_json, chrono, uuid, pcap, anyhow, thiserror — all in the suite-permitted allowlist.

Release artefacts & provenance

Every signed release ships with checksum + signature sidecars plus a CycloneDX SBOM. Enterprise procurement can pin third-party components without unpacking the binary.