Skip to content

Lease an instance for your own SITL

open_sitl leases its own instance. You need this page when something else launches SITL: a script of yours, sim_vehicle.py, a ground station test. Holding the lease is what keeps every mcardupilot session and every leasing script off that instance while yours runs.

lease_instance(owner="sim_vehicle.py quadplane, mission test")
{ "instance": 41, "mavlink_port": 6170, "json_port": 9412, "owner": "sim_vehicle.py quadplane, mission test" }

The server took the first pool instance whose lock was free and whose ports nothing holds, and keeps the lock until release_instance(41) or until the server exits. Start SITL on it:

Terminal window
sim_vehicle.py -v ArduPlane -f quadplane -I 41

-I 41 puts MAVLink on 5760 + 10 × 41 = 6170. Every other caller now sees the holder by name:

list_instances()
[ { "instance": 41, "in_pool": true, "mavlink_port": 6170, "holder": "sim_vehicle.py quadplane, mission test pid 728433",
"session_id": null, "leased_here": true, "busy_ports": [] } ]

When you are done, stop your SITL first, then give the number back:

release_instance(41)

Ask for a specific number with lease_instance(owner, instance=45). If someone holds it, the error names them; if no one holds it but its ports are taken, the error says by what.

If your script launches SITL itself, let the library launch it. Sitl leases an instance, probes its ports, launches SITL in its own process group, and on close() (or leaving the with) kills the group, waits for the ports to free and releases the lease:

from mcardupilot.sitl import Sitl
with Sitl(frame="quadplane", defaults=["/path/to/quadplane.parm"], owner="mission test") as s:
print(s.instance, s.port) # 40 6160, say
s.arm_on_pad("QLOITER")
...

instance=42 asks for one and raises InstanceBusy naming the holder. Use the library from a test harness goes further.

For a SITL you must launch some other way, hold a lease around it:

import subprocess
from mcardupilot import procs
from mcardupilot.config import Settings, mavlink_port
from mcardupilot.leases import LeasePool
settings = Settings() # the same pool and lock directory as the server
pool = LeasePool(settings.leases_dir, settings.instance_pool)
def ports_taken(n: int) -> str | None:
busy = procs.busy_ports(n)
return f"its ports are taken: {', '.join(busy)}" if busy else None
with pool.lease("nightly sweep", check=ports_taken) as lease:
n = lease.instance
subprocess.run(["sim_vehicle.py", "-v", "ArduPlane", "-f", "quadplane", "-I", str(n)])

check runs while the lock is held. The pool scan skips an instance it rejects; a requested one raises with the reason.

Terminal window
uv run python -m mcardupilot.leases status
pool 40-59, locks in ~/.local/share/mcardupilot/leases
41 tcp:6170 sim_vehicle.py quadplane, mission test pid 728433
44 tcp:6200 mcardupilot session f391b1 (tools-hop-test) pid 728436
2 of 20 held

Instances leased outside the pool, by a script with a wider pool, are listed too and marked (outside the pool).

A lease only excludes callers that take leases. autotest.py (always instance 0) and make fly take none, which is why every launcher also probes the instance’s ports under the lock, and why instance 0 cannot be pooled at all. If you launch SITL by hand on a pool instance without a lease, mcardupilot skips that instance because its ports are busy, but your SITL is unprotected against the next unleased launch.