Developer Documentation
Comprehensive guide to integrating the AeroLander deep reinforcement learning precision landing policy (30 km/h rated) with ArduPilot flight controllers, companion computers, and ROS 2 simulation environments.
Quickstart: Onboard Policy Runner
The AeroLander policy is exported as an optimized .onnx model evaluated at 50 Hz on the companion computer (Raspberry Pi 5 or NVIDIA Jetson Orin). Telemetry is ingested from the autopilot via MAVLink, transformed into body-frame tensors, and outputs collective thrust and angular rates directly.
# 1. Clone repository & install dependencies
git clone https://github.com/aerolander-tech/aerolander-edge.git
cd aerolander-edge
pip install numpy onnxruntime pymavlink opencv-contrib-python
# 2. Test policy in Software-in-the-Loop (SITL) dry-run
sim_vehicle.py -v ArduCopter --console --map
python deploy/run_policy.py --conn udp:127.0.0.1:14550 --policy models/policy.onnx --dry-run
AeroLander communicates with ArduPilot via standard MAVLink protocols. You do not need custom firmware builds; standard ArduCopter 4.4+ is supported out-of-the-box.
25-Dimensional Observation Space
The actor policy neural network evaluates a normalized 25-dimensional vector every 20ms (50 Hz):
# Observation vector layout:
obs = np.concatenate([
g_body, # [0:3] Gravity vector in body frame (unit vector)
omega / MAX_RATE, # [3:6] Body angular rates [wx, wy, wz] (rad/s)
vel_body / 10.0, # [6:9] UAV velocity in body frame (m/s)
[altitude / 10.0], # [9] Relative altitude AGL from EKF (m)
coarse_rel_body / 20.0, # [10:13] Coarse UWB / radio beacon pad position (m)
aruco_rel_body / 10.0, # [13:16] ArUco relative position from Kalman Filter (m)
aruco_vel_body / 10.0, # [16:19] ArUco relative velocity from Kalman Filter (m/s)
[visible_flag], # [19] Binary optical lock flag (1.0 = locked, 0.0 = lost)
[min(age_s, 2.0) / 2.0], # [20] Time elapsed since last valid visual detection
last_action, # [21:25] Previous policy action [thrust, rate_x, rate_y, rate_z]
]).astype(np.float32)
ArduPilot Parameters & CTBR Configuration
Configure your ArduPilot parameters via Mission Planner or MAVProxy to enable high-rate body rate control:
GUID_OPTIONS = 8(Bit 3 enabled): Instructs ArduPilot to treat the thrust field inSET_ATTITUDE_TARGETas normalized specific thrust (0.0 to 1.0) rather than climb rate (m/s).MOT_HOVER_LEARN = 2: Automatically calibrates hover throttle for the specific airframe thrust-to-weight ratio.EK3_SRC1_POSXY = 3: GPS or Optical Flow positioning source configuration.
def send_ctbr(master, throttle_fraction, rates_frd):
"""Streams SET_ATTITUDE_TARGET at 50 Hz in GUIDED mode."""
type_mask = 0b00000111 # Ignore attitude quaternion, listen only to body rates
master.mav.set_attitude_target_send(
0, # time_boot_ms
master.target_system,
master.target_component,
type_mask,
[1, 0, 0, 0], # target quaternion (ignored)
float(rates_frd[0]), # Roll rate (rad/s)
float(rates_frd[1]), # Pitch rate (rad/s)
float(rates_frd[2]), # Yaw rate (rad/s)
float(throttle_fraction) # Normalized thrust [0.0, 1.0]
)
Always fly with an RC safety pilot. Switching the flight mode switch away from GUIDED (e.g. to POSHOLD or STABILIZE) instantly severs the companion computer's authority, returning full manual control to the pilot.
Dual ArUco Marker Tracker & Kalman Filter
To maintain visual tracking from 15 meters altitude down to touchdown, AeroLander uses a dual-marker layout:
- Primary Marker (40 cm side, ID #23): Detected reliably at high altitudes (5m – 18m).
- Secondary Center Marker (10 cm side, ID #7): Detected during the final 0.5m descent when the large marker overflows the camera field-of-view.
# Sub-pixel pose estimation with OpenCV solvePnP
ok, rvec, tvec = cv2.solvePnP(
marker_3d_corners,
detected_2d_corners,
camera_matrix,
dist_coeffs,
flags=cv2.SOLVEPNP_IPPE_SQUARE
)
# Transform camera vector to body frame with extrinsic matrix
rel_body = R_CAM_TO_BODY @ tvec.flatten()
rel_world = R_body_to_world @ rel_body
# Update Kalman Filter with distance-scaled measurement variance
dist = np.linalg.norm(tvec)
kalman_filter.update(rel_world, r_variance=0.01 + 0.015 * dist)
Cloudflare Edge Telemetry Streaming
Companion computers stream high-frequency telemetry over WebSockets to Cloudflare Workers, allowing ground control stations to monitor fleet health with sub-10ms edge latency.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader === 'websocket') {
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
server.accept();
server.addEventListener('message', async (event) => {
const telemetry = JSON.parse(event.data);
// Ingest 50Hz telemetry frame & index into D1 / R2
await env.FLIGHT_LOGS_R2.put(`telemetry/${telemetry.drone_id}/${Date.now()}.json`, event.data);
});
return new Response(null, { status: 101, webSocket: client });
}
return new Response('AeroLander Edge WebSocket Server Active');
}
};