Aerie Architecture

Control Panel | API Docs

What is Aerie?

Aerie is a REST/WebSocket API that wraps MAVSDK-Python in a FastAPI server. It lets you control multiple drones (real or simulated) over HTTP — arm, takeoff, fly to coordinates, run missions, stream telemetry — with auto-generated OpenAPI documentation. A single Aerie instance manages all drones via a registry, with each drone addressed by a unique ID.

System Architecture

┌──────────────────────────────────┐ Browser (:8000) ──HTTP/WS──> │ Aerie (FastAPI) │ │ │ │ DroneRegistry │ │ ├── "alpha" ─> MAVSDK System ──┼── MAVLink :14540 ──> PX4 #0 │ ├── "bravo" ─> MAVSDK System ──┼── MAVLink :14541 ──> PX4 #1 │ └── "charlie"─> MAVSDK System ──┼── MAVLink :14542 ──> PX4 #2 │ │ └──────────────────────────────────┘ │ ┌─────▼──────────────────────┐ Browser (:6080) <──noVNC── │ Docker Container │ │ PX4 #0 + PX4 #1 + PX4 #2 │ │ + Gazebo (shared world) │ │ + Xvfb/VNC │ └────────────────────────────┘

Multi-Drone Design

DroneRegistry (replaces the old singleton)

Previously, Aerie used a DroneManager singleton that held a single MAVSDK System instance. The new DroneRegistry holds a dict[str, System] — one MAVSDK connection per drone ID. Each drone is independently connected and controlled.

MAVSDK-Python supports multiple System() instances in one process, each connecting to a different drone via a separate gRPC server. This means no coordinator server is needed — one Aerie instance handles all drones.

API Route Structure

All drone-specific endpoints are namespaced under /api/v1/drones/{drone_id}/...:

POST   /api/v1/drones                           # Register a new drone
GET    /api/v1/drones                           # List all drones
GET    /api/v1/drones/{drone_id}                # Get drone status
DELETE /api/v1/drones/{drone_id}                # Remove a drone

POST   /api/v1/drones/{drone_id}/action/arm     # Arm specific drone
POST   /api/v1/drones/{drone_id}/action/takeoff  # Takeoff specific drone
GET    /api/v1/drones/{drone_id}/telemetry/all   # Telemetry for specific drone
WS     /api/v1/drones/{drone_id}/ws/telemetry    # Real-time stream for specific drone

Adding a Drone (Flow)

  1. Operator calls POST /api/v1/drones with {"drone_id": "alpha", "address": "udpin://0.0.0.0:14540"}
  2. Aerie creates a new MAVSDK System(), connects to the drone via MAVLink
  3. The drone is stored in the registry and immediately available for commands
  4. To remove: DELETE /api/v1/drones/alpha

Component Deep Dive

PX4 Autopilot (Flight Controller)

PX4 is open-source flight controller firmware that runs on real drone hardware. In SITL (Software-In-The-Loop) mode, it runs on a normal computer and simulates the flight controller. It exposes a MAVLink interface on UDP port 14540 — the same binary protocol used by real drones.

PX4 receives commands (arm, takeoff, goto) and sends back telemetry (GPS, battery, attitude). It handles all the low-level flight control: PID loops, sensor fusion, safety checks.

For multi-drone simulation, multiple PX4 instances run in the same container, each with a unique PX4_INSTANCE number. Instance N listens on port 14540 + N.

Gazebo (Physics Engine + 3D Renderer)

Gazebo serves two roles:

  1. Physics simulation — simulates gravity, motor thrust, aerodynamics, collisions. PX4 sends motor commands to Gazebo; Gazebo computes what happens physically and sends back simulated sensor data (GPS, IMU, barometer).
  2. 3D rendering — renders the drone and world as a 3D scene. This is what you see in the Gazebo window.

All drones share the same Gazebo world, so you can see them all in the 3D view simultaneously.

VNC (Virtual Network Computing)

The challenge: Gazebo renders a GUI, but it's running inside a Docker container with no monitor. The solution is a 3-layer chain:

  1. Xvfb (X Virtual Framebuffer) — creates a fake "monitor" in memory. Gazebo renders to this virtual display (display :99).
  2. x11vnc — captures the virtual framebuffer and serves it over the VNC protocol (port 5900).
  3. noVNC + websockify — noVNC is a JavaScript VNC client that runs in a browser. Served on port 6080.

So the chain is: Gazebo → Xvfb → x11vnc → websockify → your browser.

Aerie API Server

A Python FastAPI application that translates HTTP requests into MAVLink commands:

Running on Different Platforms

macOS (current setup)

Requires Docker Desktop for Mac. Everything else runs natively.

# Install
python3 -m venv .venv && source .venv/bin/activate
pip install -e .

# Run (2 drones by default)
./start.sh

# Or run with a specific number of drones
NUM_DRONES=3 ./start.sh

Windows

Requires Docker Desktop for Windows with WSL2 backend enabled.

# In PowerShell or Windows Terminal:

# 1. Install Docker Desktop and enable WSL2 backend

# 2. Clone the repo and set up Python
python -m venv .venv
.venv\Scripts\activate
pip install -e .

# 3. Build and start the simulation container (2 drones)
set NUM_DRONES=2
docker compose up -d --build

# 4. Wait ~40 seconds for PX4 to initialize, then start Aerie
set PYTHONPATH=src
uvicorn aerie.main:app --host 0.0.0.0 --port 8000

Note: On AMD64/Intel machines, change platform: linux/arm64 to linux/amd64 in docker-compose.yml.

Linux

Same as macOS. Install Docker Engine (not Desktop), Python 3.10+, then:

python3 -m venv .venv && source .venv/bin/activate
pip install -e .
NUM_DRONES=3 ./start.sh

On AMD64 Linux, change platform: linux/arm64 to linux/amd64 in docker-compose.yml.

API Reference Summary

Full interactive docs at /docs (Swagger UI).

EndpointMethodDescription
/api/v1/dronesPOSTRegister a new drone ({"drone_id": "alpha", "address": "udpin://0.0.0.0:14540"})
/api/v1/dronesGETList all registered drones
/api/v1/drones/{id}GETGet drone status
/api/v1/drones/{id}DELETERemove a drone
/api/v1/drones/{id}/action/armPOSTArm motors
/api/v1/drones/{id}/action/disarmPOSTDisarm motors
/api/v1/drones/{id}/action/takeoffPOSTTakeoff ({"altitude_m": 5.0})
/api/v1/drones/{id}/action/landPOSTLand at current position
/api/v1/drones/{id}/action/gotoPOSTFly to GPS position
/api/v1/drones/{id}/action/return-to-launchPOSTReturn to home
/api/v1/drones/{id}/action/holdPOSTHold position
/api/v1/drones/{id}/action/killPOSTEmergency motor kill
/api/v1/drones/{id}/missionPOSTUpload mission waypoints
/api/v1/drones/{id}/missionGETDownload current mission
/api/v1/drones/{id}/missionDELETEClear mission
/api/v1/drones/{id}/mission/startPOSTStart mission
/api/v1/drones/{id}/mission/pausePOSTPause mission
/api/v1/drones/{id}/mission/progressGETCurrent waypoint progress
/api/v1/drones/{id}/telemetry/positionGETGPS position
/api/v1/drones/{id}/telemetry/attitudeGETRoll/pitch/yaw
/api/v1/drones/{id}/telemetry/batteryGETBattery voltage/remaining
/api/v1/drones/{id}/telemetry/healthGETSensor health checks
/api/v1/drones/{id}/telemetry/flight-modeGETCurrent flight mode
/api/v1/drones/{id}/telemetry/allGETFull telemetry snapshot
ws://.../api/v1/drones/{id}/ws/telemetryWSReal-time telemetry stream

Key Files

FilePurpose
src/aerie/main.pyFastAPI app entry point, CORS, exception handlers, lifespan
src/aerie/config.pyPydantic settings (env vars: PORT, CORS, etc.)
src/aerie/core/drone.pyDroneRegistry managing multiple MAVSDK System instances
src/aerie/api/routes/drones.pyDrone registration/removal endpoints
src/aerie/api/routes/action.pyAction endpoints (arm, takeoff, land, goto, etc.)
src/aerie/api/routes/mission.pyMission CRUD + start/pause/progress
src/aerie/api/routes/telemetry.pyREST telemetry polling endpoints
src/aerie/api/websockets/telemetry.pyWebSocket telemetry streaming with subscription model
src/aerie/services/telemetry_service.pyBridges MAVSDK async telemetry to WebSocket broadcasts
sim/DockerfileExtends headless PX4 image with VNC for GUI access
sim/entrypoint.shStarts Xvfb, x11vnc, noVNC, then N PX4 instances + Gazebo
docker-compose.ymlContainer orchestration (NUM_DRONES, ports, env vars)

Aerie — MAVSDK REST API for multi-drone control