WarDragon FPV Energy Scan
February 3, 2026 · View on GitHub
This repo contains the FPV energy scan workflow used by WarDragon. It scans known FPV center
frequencies with GNURadio + gr-inspector and can optionally confirm detections via suscli fpvdet.
Alerts are published over ZMQ in a message format compatible with the WarDragon ingestion pipeline.
Note: the suscli fpvdet detector plugin is not open source and is not included in this repo.
WarDragon kits ship with it installed; other systems need their own licensed build.
Script
scripts/fpv_energy_scan.py
Requirements
- GNU Radio 3.10.x
- gr-inspector (GNURadio OOT module, installed system-wide)
- gr-osmosdr + SoapySDR (Pluto supported)
suscliwith fpvdet plugin (optional; required for confirm step)- A calibrated
suscliprofile (example:fpv58_race_2m)
ZMQ endpoints
Defaults (override with env vars or CLI flags):
- FPV XPUB:
FPV_ZMQ_ENDPOINT=tcp://127.0.0.1:4226 - WarDragon monitor GPS (optional):
WARD_MON_ZMQ=tcp://127.0.0.1:4225
The FPV publisher uses XPUB and emits a JSON list of message objects matching the DJI receiver format (Basic ID, Location/Vector Message if GPS available, Self-ID Message, Frequency Message, Signal Info).
Environment overrides
FPV_ZMQ_ENDPOINT: XPUB endpoint for FPV alertsWARD_MON_ZMQ: monitor GPS endpointWARD_MON_RECV_TIMEOUT_MS: monitor recv timeout
DJI Guard (AntSDR)
When using an AntSDR in DJI receiver mode, the service wrapper can SSH into the SDR to stop the DJI daemon before scanning (they share the same RF hardware).
FPV_DJI_GUARD: Set to1to enable SSH guard (default),0to disableFPV_DJI_GUARD_INTERVAL: Seconds between guard checks (default: 30)FPV_DJI_GUARD_VERBOSE: Set to1for verbose SSH output (set automatically with-d)
Example to disable the DJI guard (if not using AntSDR DJI mode):
FPV_DJI_GUARD=0 ./scripts/fpv_energy_service.sh -z --zmq-endpoint tcp://127.0.0.1:4226
Running
python3 scripts/fpv_energy_scan.py
This workflow is intended for the WarDragon kit.
Service wrapper (stops AntSDR DJI, runs scan, then restarts DJI on exit):
scripts/fpv_energy_service.sh -z --zmq-endpoint tcp://127.0.0.1:4226
Optional systemd unit:
sudo cp scripts/fpv-receiver.service /etc/systemd/system/fpv-receiver.service
sudo systemctl daemon-reload
sudo systemctl start fpv-receiver
Important: The service file includes User=dragon and Group=dragon. If your username
is different, edit the service file before copying:
# Check your username
whoami
# Edit if needed (replace 'dragon' with your username)
sed -i 's/dragon/yourusername/g' scripts/fpv-receiver.service
sudo cp scripts/fpv-receiver.service /etc/systemd/system/
sudo systemctl daemon-reload
The service must run as your user (not root) because suscli calibration profiles are
stored in your home directory (~/.config/suscan/). Running as root will fail to find them.
Enable ZMQ output and custom endpoints:
python3 scripts/fpv_energy_scan.py -z --zmq-endpoint tcp://127.0.0.1:4226 \
--monitor-endpoint tcp://127.0.0.1:4225
Debug output:
python3 scripts/fpv_energy_scan.py -d
Use a different SDR (gr-osmosdr args):
python3 scripts/fpv_energy_scan.py --osmosdr-args "driver=soapy,soapy=hackrf" \
--samp-rate 8e6 --bandwidth 8e6 --gain 20
Use a different SDR via the service wrapper:
FPV_OSMOSDR_ARGS="soapy=driver=hackrf" ./scripts/fpv_energy_service.sh
Override the Pluto address used by the service wrapper:
FPV_PLUTO_URI="ant.local" ./scripts/fpv_energy_service.sh
Notes
- The script stops GNURadio before running
suscliconfirm, then restarts it. - If
suscli fpvdetis unavailable, confirm is skipped and only energy alerts are published. - If Soapy/Pluto buffer errors occur, the script retries with backoff.
- Calibration/profile settings must match
suscliconfirm parameters (bandwidth, dt, Q, rate). - A startup warm-up sweep is used to estimate a global noise floor when fixed thresholding is enabled. If a strong transmitter is on during warm-up, the threshold may be biased high.
- Edit constants at the top of the script to change center lists, dwell/settle times, thresholds, and confirm profile settings.
How Detection Works (Summary)
- The script tunes across known FPV center frequencies and runs the gr-inspector energy detector at each center for a short dwell.
- Any detected signal wider than
MIN_BW_HZis treated as a candidate. - Candidates are confirmed with
suscli fpvdet, which estimates PAL/NTSC confidence. - If ZMQ is enabled, the script publishes both an energy alert and a confirm alert.
Tuning Guide
Key parameters near the top of scripts/fpv_energy_scan.py:
MIN_BW_HZ: Minimum detected bandwidth to accept. Typical analog FPV is ~6–8 MHz. Use 4e6 for fewer false positives; use 2e6 if the detector underestimates bandwidth.SETTLE_S: Retune settle time. Too low can cause stale samples.DWELL_S: How long to observe each center. Longer increases detection but slows the sweep.AUTO_THRESHOLD: If True, gr-inspector estimates the noise floor every dwell. If False, a fixed threshold is used.THRESHOLD_DB: Fixed threshold (used when auto is off).THRESHOLD_OFFSET_DB: When auto is off, a warm-up sweep computes a global noise median and this offset is added (e.g., noise -91 dB + offset 6 dB = threshold -85 dB).
Suggested defaults for outdoor use:
MIN_BW_HZ = 4e6THRESHOLD_OFFSET_DB = 6.0
If you miss weak signals:
- Lower
THRESHOLD_OFFSET_DBby 1–2 dB, or - Drop
MIN_BW_HZto 2e6.
If you see too many false positives:
- Raise
THRESHOLD_OFFSET_DBby 1–2 dB, or - Increase
MIN_BW_HZtoward 6e6.
Signal Data Format
The suscli fpvdet plugin outputs signal strength as linear power (magnitude², 0-1 scale),
not dBm. This is important for downstream processing:
| Field | Type | Description |
|---|---|---|
rssi | float | Linear power (0-1 scale, higher = stronger) |
signal.pal | float | PAL video confidence (0-100%) |
signal.ntsc | float | NTSC video confidence (0-100%) |
Note: The rssi value cannot be converted to distance without SDR calibration. It's useful
for relative signal strength comparisons (e.g., weighted triangulation with multiple kits) but
not for absolute range estimation.
Troubleshooting
Service runs but no detections appear
-
Check user: The service must run as your user, not root. Verify with:
grep "User=" /etc/systemd/system/fpv-receiver.serviceShould show your username (e.g.,
User=dragon). -
Check calibration: Ensure your suscli profile exists:
ls ~/.config/suscan/profiles/Should include
fpv58_race_2mor your configured profile. -
Test manually: Run the script directly to see output:
python3 scripts/fpv_energy_scan.py -z -d --zmq-endpoint tcp://127.0.0.1:4226
SSH errors in service logs
If using the DJI guard with AntSDR and seeing SSH failures:
-
SDR not in DJI mode: If your SDR firmware isn't running DJI services, disable the guard:
# In the service file, add: Environment=FPV_DJI_GUARD=0 -
SSH timeout/connection refused: The guard will retry silently and continue scanning. This is normal if the SDR is unreachable.
Script exits immediately
Check for set -e failures. The service wrapper uses strict error handling. Common causes:
- Missing dependencies (gr-inspector, suscli)
- SDR not connected
- Permission denied on SDR device (add user to
plugdevgroup)