Julian Reder AERPAW · 2025 - Present

aerpawlib

Official vehicle-control library for AERPAW, NC State's NSF testbed for 5G/6G and autonomous aircraft.

2025 - Present
PythonMAVSDK / MAVLinkArduPilotasyncio / gRPCZeroMQpytestSITL
Experiment script Runner / StateMachine aerpawlib v1 / v2 Vehicle, can_*, ZMQ MAVSDK + gRPC mavsdk_server MAVLink filter takeoff land goto yaw Cube Orange ArduPilot, LAM6/SAM4 Safety checker KML geofence, C-VM SITL tests Copter + Rover ZMQ proxy 5570 / 5571 JSON control plane, wait_for_peers
Experiment script → aerpawlib → MAVSDK → the testbed MAVLink filter → Cube Orange / ArduPilot.

Built

I maintain AERPAW/aerpawlib — the Python API experimenters use to fly LAM6/SAM4 UAVs and the rover. I moved it from DroneKit onto MAVSDK, kept the v1 Runner/StateMachine surface, and added a v2 async API. Current release is 1.4.11.

Hard parts

  • Testbed MAVLink filter only allows takeoff, land, goto, and yaw (20–100 m takeoff, land within 5 m of home).
  • SET_MODE is blocked; GUIDED has to be held with MAV_CMD_DO_SET_MODE. LOITER drops the E-VM link.
  • Safety checker belongs on the C-VM, not localhost or the OEO console.
  • Timed set_velocity was killing newer commands; bearings ignored latitude (~51° vs 45° at 35.7°N).
  • Multi-vehicle ZMQ dropped transitions until JSON + ACK + wait_for_peers.

Learned

  • The filter defines the API as much as the SDK does.
  • SITL is necessary and not sufficient.
  • A compat layer plus a second API beats a flag-day rewrite.

Also

  • Primary maintainer of the official AERPAW.org library (v1 + v2 on MAVSDK 3.17)
  • Safety checker with KML geofences, fail-closed command validation, success-only RTL
  • ZMQ XSUB/XPUB proxy for multi-vehicle scripts (ports 5570/5571, JSON control plane)
  • ArduPilot SITL + MAVSDK integration tests; CI on Python 3.10–3.14
  • Docs at aerpaw.github.io/aerpawlib

The platform

AERPAW — Aerial Experimentation and Research Platform for Advanced Wireless — is NC State's NSF PAWR testbed. The public line is that it is the first platform built to study 5G/6G together with autonomous aircraft. The vehicles experimenters actually fly are the LAM6 and SAM4 multicopters and a rover, all on Cube Orange running ArduPilot, speaking MAVLink v2. Portable radio nodes (SDRs on the large vehicles, lighter radios on SAM4) are the payload. Experiment VMs start through startexperiment.sh → startVehicle.sh.

A MAVLink filter sits between experimenter code and the autopilot. Only takeoff, land, simple goto, and yaw go through. Takeoff is 20–100 m. Landing has to be within 5 m of the takeoff point. Gotos are checked against the field geofence; the rover also has no-go zones for creeks. Status messages flow the other way unrestricted. That filter is the real API.

What I maintain

The original aerpawlib was John Kesler's DroneKit / pymavlink library: experimenters wrote Runner and StateMachine scripts, asyncio in the background, QGroundControl .plan files for waypoints. I am a listed co-author and the primary maintainer of the official AERPAW/aerpawlib tree. Current release is 1.4.11.

I migrated the library onto MAVSDK so vehicle firmware can move without monkey-patching Python. v1 keeps the old surface — DroneKit-shaped telemetry wrappers, a dual asyncio/gRPC loop so existing sync-looking scripts still run. v2 is the API I wanted: VehicleTask, can_takeoff / can_goto / can_land, one event loop. I also own the SITL harness, the CLI, the docs, and the 1.4.x series that made this survive the live E-VM / C-VM filter.

What the filter taught the API

SET_MODE is not on the allow-list. MAV_CMD_DO_SET_MODE is. action.hold() / LOITER severs the E-VM link, so GUIDED has to be held with raw MAVLink. The safety checker has to talk to this node's C-VM, not localhost and not the OEO console — that bug shipped and got reverted in 1.4.7–1.4.8. Auto RTL runs only after a successful mission; abort used to fly home at home.alt ≈ 0 and get rejected by the 20 m minimum.

Smaller, uglier ones: timed set_velocity loops cancelled the next command; geodesic bearings ignored latitude (about 51° vs 45° at Lake Wheeler); ExternalProcess dropped stdin/stdout files. Multi-vehicle ZMQ started as fire-and-forget PUB/SUB. It drops transitions. The control plane is now JSON with ACK, HELLO, and wait_for_peers on ports 5570/5571.

E-VM script aerpawlib commands MAVLink filter (allow-list) takeoff 20-100 m, land <=5 m of home takeoff / land goto, yaw status out unrestricted blocked: SET_MODE, LOITER GUIDED via MAV_CMD_DO_SET_MODE Cube Orange ArduPilot, MAVLink v2 C-VM safety checker KML geofence on this node
The allow-list is the API. Status comes back unrestricted; SET_MODE and LOITER do not go through.

How I know it works

Unit tests run on Python 3.10–3.14 in CI. Integration tests start ArduCopter and Rover SITL plus mavsdk_server and reset the vehicles between cases. MAVProxy will block if stdout is a pipe — the suite has to run with capture off. Docs live at aerpaw.github.io/aerpawlib. The useful lesson is boring: SITL is necessary and not sufficient. The filter wins arguments.