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)
  • suscli with fpvdet plugin (optional; required for confirm step)
  • A calibrated suscli profile (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 alerts
  • WARD_MON_ZMQ: monitor GPS endpoint
  • WARD_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 to 1 to enable SSH guard (default), 0 to disable
  • FPV_DJI_GUARD_INTERVAL: Seconds between guard checks (default: 30)
  • FPV_DJI_GUARD_VERBOSE: Set to 1 for 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 suscli confirm, then restarts it.
  • If suscli fpvdet is 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 suscli confirm 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)

  1. The script tunes across known FPV center frequencies and runs the gr-inspector energy detector at each center for a short dwell.
  2. Any detected signal wider than MIN_BW_HZ is treated as a candidate.
  3. Candidates are confirmed with suscli fpvdet, which estimates PAL/NTSC confidence.
  4. 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 = 4e6
  • THRESHOLD_OFFSET_DB = 6.0

If you miss weak signals:

  • Lower THRESHOLD_OFFSET_DB by 1–2 dB, or
  • Drop MIN_BW_HZ to 2e6.

If you see too many false positives:

  • Raise THRESHOLD_OFFSET_DB by 1–2 dB, or
  • Increase MIN_BW_HZ toward 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:

FieldTypeDescription
rssifloatLinear power (0-1 scale, higher = stronger)
signal.palfloatPAL video confidence (0-100%)
signal.ntscfloatNTSC 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

  1. Check user: The service must run as your user, not root. Verify with:

    grep "User=" /etc/systemd/system/fpv-receiver.service
    

    Should show your username (e.g., User=dragon).

  2. Check calibration: Ensure your suscli profile exists:

    ls ~/.config/suscan/profiles/
    

    Should include fpv58_race_2m or your configured profile.

  3. 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 plugdev group)