Run up to 10 SITL drones for testing
  • Python 81.7%
  • HTML 15.3%
  • Batchfile 2%
  • Shell 1%
Find a file
2026-07-03 10:24:42 +02:00
docs/plans feat: replace Qt GUI with web UI, asyncio process management 2026-07-02 12:39:01 +02:00
src feat: RGC-first networking - conditional QGC push + device-id prefix, drop mDNS field 2026-07-03 10:24:42 +02:00
tests feat: RGC-first networking - conditional QGC push + device-id prefix, drop mDNS field 2026-07-03 10:24:42 +02:00
.gitignore feat: frame preset manager - create/edit/clone/download/upload/delete 2026-07-02 22:42:32 +02:00
build.bat Add tunerview live-flight tuning viewer 2026-05-28 13:51:37 +02:00
build.sh Add tunerview live-flight tuning viewer 2026-05-28 13:51:37 +02:00
LICENSE Initial commit: simlauncher — multi-drone SITL launcher 2026-05-26 07:58:29 +02:00
pyproject.toml fix: mavproxy logfile/state dir + declare undeclared deps 2026-07-02 19:52:36 +02:00
QUICKSTART.md feat: RGC-first networking - conditional QGC push + device-id prefix, drop mDNS field 2026-07-03 10:24:42 +02:00
README.md feat: RGC-first networking - conditional QGC push + device-id prefix, drop mDNS field 2026-07-03 10:24:42 +02:00
Screenshot 2026-05-29 120124.png feat: multi-session support with global 10-drone ceiling 2026-07-02 13:34:57 +02:00
simlauncher.bat bats: auto-recover from venvs with wrong-ABI compiled extensions 2026-05-28 11:04:47 +02:00
simlauncher.sh bats: auto-recover from venvs with wrong-ABI compiled extensions 2026-05-28 11:04:47 +02:00
tunerview.bat Add tunerview live-flight tuning viewer 2026-05-28 13:51:37 +02:00
tunerview.sh Add tunerview live-flight tuning viewer 2026-05-28 13:51:37 +02:00
uv.lock fix: mavproxy logfile/state dir + declare undeclared deps 2026-07-02 19:52:36 +02:00

simlauncher

Launch and manage multiple ArduCopter SITL drones for testing QGroundControl (QGC) and Rift Ground Control (RGC). Web UI by default — runs headless on a Raspberry Pi 5 and is driven from any browser on the LAN. An optional desktop (Qt) UI is available for local use.

  • Targets: Linux ARM64 (Raspberry Pi 5 / Debian / Ubuntu), Linux x86_64, macOS arm64.
  • SITL binaries: auto-downloaded from Unmanned-Software-Solutions/ardupilot-sitl-binaries on first run (or point SIMLAUNCHER_BINARY at your own).
  • Rift integration: can launch rift-drone-services and MAVProxy per drone so RGC discovers the sim drones over mDNS exactly like real hardware.

Two UI modes

Mode How to launch Needs Use
Web (default) uv run simlauncher → open http://<host>:8080 base deps only Headless Pi, shared/remote control, multiple operators
Desktop (Qt) uv run simlauncher --gui --extra gui Local workstation, single user

The persisted default is stored in config (ui_mode); the web UI's ⚙ settings can flip it and restart. --web / --gui on the command line override it for that launch.

Quick start (Raspberry Pi 5 / Linux)

# 1. install uv (manages Python 3.12 + deps)
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.local/bin/env

# 2. clone + install
git clone git@forge.riftdynamics.io:MasterPotato/SimLauncher.git ~/simlauncher
cd ~/simlauncher
uv sync                       # base (web) deps
# uv sync --extra mavproxy     # add if you'll use the MAVProxy / RGC path
# uv sync --extra gui          # add if you'll use the desktop UI or tunerview

# 3. run the web UI
uv run simlauncher --host 0.0.0.0 --port 8080

Then open http://<pi-ip>:8080 from any machine on the LAN.

The SITL binary auto-downloads on first launch and caches under ~/.simlauncher/bin/<platform>/bin/ArduCopter. Override with SIMLAUNCHER_BINARY=/path/to/ArduCopter.

Web UI

The header shows the global drone capacity (default 10, set with --max-drones). Work is organised into sessions — each is an independent group of drones with its own config. Click + Session to add one (disabled when the pool is full); sessions are shared across every browser and survive a page refresh.

Per session:

Field Meaning
Drones How many SITL instances (capped to remaining pool slots)
Base Lat/Lng/Alt/Hdg Home; drones auto-space in a ~3 m grid
QGC / GCS IP Optional. Set a plain QGC computer's IP to also push MAVLink there. Blank = RGC discovery only (the custom QGC connects via Rift discovery)
Frame Physics model (Stock quad, or Tulip 2 kg)
Rift Services Launch rift-drone-services per drone for RGC discovery (needs the binary path + D-Bus)
MAVProxy Route MAVLink through MAVProxy so RGC can connect to the advertised port (needs the [mavproxy] extra)
Prefix (RGC) rift-drone-services device-id prefix — drones are <prefix>-01, <prefix>-02, … and RGC matches them by prefix. Blank = sim

Hit Start on a session; live per-drone logs stream into tabs over WebSocket.

Ports

For global drone index N (0-based, across all sessions):

Consumer Port Notes
QGC 14550 + N SITL (or MAVProxy) pushes MAVLink here; QGC listens
RGC 14650 + N MAVProxy udpin; rift-drone-services advertises this over mDNS
SITL→MAVProxy (internal) 14540 + N Only when MAVProxy is enabled

QGroundControl setup

  1. QGC / GCS IP in the session = your computer's IP (or blank for the same machine).
  2. In QGC → Application Settings → Comm Links, add a UDP link listening on each drone port (14550, 14551, …). QGC's default only covers 14550, so add one per drone.
  3. Allow inbound UDP through your firewall. On Windows (PowerShell as Administrator):
    New-NetFirewallRule -DisplayName "QGC MAVLink SITL" -Direction Inbound -Action Allow -Protocol UDP -LocalPort 14550-14559 -Profile Any
    

Rift Ground Control (RGC) / rift-drone-services

To make sim drones discoverable by RGC exactly like real hardware:

  1. Build rift-drone-services for the host (from the rift-drone repo) and note its path.
  2. Install MAVProxy: uv sync --extra mavproxy (or sudo apt install python3-mavproxy where available).
  3. In the session: tick Rift Services (point it at the binary), tick MAVProxy, and optionally set a Prefix so RGC can match this batch of drones (<prefix>-01, …). Leave QGC / GCS IP blank — RGC connects via discovery, not a pushed stream.

RGC then discovers each drone over mDNS (_rift_drone._tcp), reads the advertised MAVLink port, and connects through MAVProxy. rift-drone-services runs with --mock-modem --dry-run for sim. D-Bus must be running (default on Raspberry Pi OS).

Running as a service (Pi)

# /etc/systemd/system/simlauncher.service
[Unit]
Description=simlauncher SITL web UI
After=network-online.target
Wants=network-online.target

[Service]
User=admin
WorkingDirectory=/home/admin/simlauncher
ExecStart=/home/admin/.local/bin/uv run --extra mavproxy simlauncher --host 0.0.0.0 --port 8080
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now simlauncher
journalctl -u simlauncher -f

Update: git pull && sudo systemctl restart simlauncher (a pull alone changes nothing until the service restarts).

Desktop UI

For a local workstation with a display:

uv sync --extra gui --extra mavproxy
uv run simlauncher --gui

Same capabilities as the web UI (optional GCS IP, Rift Services, MAVProxy, device prefix) in a Qt window.

Scaling the ceiling

--max-drones bounds the total across all sessions (default 10, a comfortable Pi 5 limit). On a faster dedicated machine, raise it:

uv run simlauncher --max-drones 40

Each SITL instance is roughly half a core + ~80100 MB RAM at 1× speedup.

Frame profiles

  • Stock quad — built-in 1.5 kg X-quad, generic tune.
  • Tulip 2 kg (10-inch, 6S) — Rift's 10-inch airframe; mass, hover throttle, battery, and drag calibrated from a real flight log so simulated cruise speed-vs-pitch-vs-throttle matches the real drone. Ships a matching --defaults param file.

Add more: drop src/simlauncher/resources/<airframe>.json (schema mirrors tulip_2kg.json) and add a line to bundled_frame_profiles() in core/config.py.

Config & cache

  • Config: ~/.simlauncher/config.json (SIMLAUNCHER_CACHE_DIR overrides the base dir).
  • SITL binary cache: ~/.simlauncher/bin/<platform>/bin/.
  • Per-drone run dirs + logs: ~/.simlauncher/run/drone-<N>/.

Troubleshooting

Symptom Fix
Plain QGC sees nothing Set GCS IP to your computer's IP (blank = RGC-only, no push); add a QGC UDP comm link per port; open the firewall (above).
RGC doesn't discover the drones Make sure Rift Services + MAVProxy are ticked and rift-drone-services is running; check the drones' device-id Prefix matches what RGC expects.
MAVProxy checkbox does nothing / errors Install it: uv sync --extra mavproxy.
First run: "GitHub API rate limit" Set GITHUB_TOKEN (any read PAT) or SIMLAUNCHER_BINARY=/path/to/ArduCopter.
Drone → "crashed" Open its log tab; usual cause is a UDP port already in use.
systemctl restart hangs Fixed in current versions (bounded graceful shutdown); the first restart onto the fix may still be slow — be patient once.

Tunerview (sibling tool)

A live MAVLink tuning viewer (target-vs-actual ground speed, attitude rates, vibration, motor PWM). Needs the [gui] extra.

uv sync --extra gui
uv run tunerview --udp-host 0.0.0.0 --udp-port 15550

Front it with MAVProxy for a bidirectional link (tunerview bumps message-interval rates):

mavproxy.py --master=udp:127.0.0.1:14550 --out=udp:127.0.0.1:14551 --out=udp:127.0.0.1:15550

Then point QGC/MP at 14551 and tunerview at 15550. Offline replay: tunerview --tlog-file <path> or tunerview --log-dir <dir> (newest closed .tlog).

Development

uv sync --extra gui --extra mavproxy   # everything, so no tests skip
uv run pytest -q                       # full suite

Tests: per-module units in tests/test_<module>.py; asyncio integration (real subprocesses, mock binary) in tests/test_launcher_integration.py; web API in tests/test_web_server.py; desktop GUI in tests/test_gui.py (skips without the gui extra). Mock binaries under tests/fixtures/.

License

MIT (see pyproject.toml).