Adding a Vehicle Type, Unit, or Platform¶
This guide shows how to add a new airframe to AirStack at each level of the vehicle hierarchy (fleets guide): a platform class is code (interface, controller — px4_multirotor is the only one today), a vehicle type is data (config/vehicles/<name>/vehicle.yaml), and a vehicle unit is one serial number's calibration overlay. A fleet entry binds all three to a robot instance. The three asks are very different sizes: a new type is a YAML file, a new unit is a gitignored directory, a new compute platform is a Docker build chain.
The canonical schema reference is config/vehicles/README.md — this guide doesn't restate it.
A. Adding a new vehicle type¶
The common case: a different airframe or sensor suite on the existing px4_multirotor platform.
-
Copy the reference type.
quad_default(the Pegasus Iris with stereo camera + 3D lidar) is the only in-tree type and the template: -
Edit
config/vehicles/my_quad/vehicle.yamlper the schema README: setname:, keepplatform: px4_multirotor(the only platform today), pointairframe.base_urdf:at your URDF (package-relative, exactly whatURDF_FILEcarries — the file must exist under the robot description packages), and declare each sensor as one{type, id, frame, driver, sim}entry.sim_asset:names the sim asset — but note vehicles are pass-through in sim today:fleet_spawn.pyspawns the Pegasus Iris asset for everypx4_multirotorregardless. What does take effect per vehicle is the sensor list: anylidar*-typed entry enables the RTX lidar subgraph for that robot, anystereo_camentry the camera subgraph (the per-vehicleENABLE_LIDARequivalent). -
Reference it from a fleet file. Copy
config/fleets/sim_one_default.yamland change the vehicle binding: -
Validate before launching. The resolver names schema errors (unknown vehicle, missing
airframe.base_urdf, unknown keys): -
Launch and verify in the container.
airstack up --fleet my_fleet --sim isaac, thenairstack ready. Inside the robot container,tools/fleet/resolve_fleet.pyhas resolved the whole entry fromFLEET_CONFIG_FILE(called byrobot/docker/.bashrc); confirm the exports:
Caveats worth knowing: an explicitly set env var wins over the resolver per variable (URDF_FILE, ROBOT_NAME, …), and airstack up --sim airsim keeps exporting ms-airsim's reduced stereo-only URDF regardless of the manifest. Vehicle types beyond trunk are intended to arrive as data modules (type: data, RFC #379) rather than commits to config/vehicles/.
B. Adding a new unit of an existing type¶
A unit is one physical serial number whose calibration drifts and gets re-measured — it never touches the shared type. Full story: config/local/README.md.
-
Create the calibration overlay on the machine that flies (or resolves) that airframe —
config/local/is gitignored, per-machine: -
Bind it in the fleet entry:
-
Consume it via
CALIBRATION_DIR. The resolver exportsCALIBRATION_DIR=/root/AirStack/config/local/calibration/SN-0042into the robot container (config/is bind-mounted read-only at/root/AirStack/config); point driver configs at$CALIBRATION_DIR. It is empty when the fleet entry declares nounit:. Verify:
Recalibrating in the field writes into the unit directory — never into config/vehicles/<name>/.
C. Adding a new compute platform¶
This is a much bigger lift than A or B: a platform is a Docker compose profile + build-arg chain, and there are exactly two non-desktop precedents — Jetson L4T (l4t profile) and ModalAI VOXL 2 (voxl profile), both in robot/docker/docker-compose.yaml. Read Docker build profiles and the docker-build-profiles skill first; current status of every platform is in Supported Platforms.
-
Add a service block in
robot/docker/docker-compose.yamlunder its own profile, extendingrobot_basefromrobot-base-docker-compose.yaml. Everything builds from the singleDockerfile.robot; the variant is selected bybuild.args:BASE_IMAGE,ROS_DISTRO: jazzy,REAL_ROBOT: true,TARGET_ARCH: aarch64(both hardware precedents), and a quotedPYTHON_VERSIONwhen needed ("3.12"— unquoted YAML parses it as float3.1). -
Study the precedent closest to your board. L4T is the full worked example: an intermediate
robot-l4t-stack-baseimage (Dockerfile.l4t-stack-baseondustynv/ros:jazzy-ros-base-r36.4.0-cu128-24.04) feedsDockerfile.robotasBASE_IMAGE, both withnetwork: hostunderbuild:(a Jetson build workaround), plusruntime: nvidia,network_mode: host, and a companionzed-l4tdriver service. VOXL is the minimal CUDA-less variant:BASE_IMAGE: ubuntu:24.04,deploy: !reset {}to drop the GPU reservation, and a compute-constrained default ofAIRSTACK_STACK_DIR=.../stacks/lite_default. -
Set the runtime environment the way both precedents do:
ROBOT_NAME_SOURCE=hostname(real robots resolve identity from the host, not the container name),LAUNCH_PACKAGE=autonomy_bringup(no RViz), an overridableAIRSTACK_STACK_DIRdefault, and a tmuxautolaunch ... sim:=falsecommand. -
Give the image both cache tags. Every service with a
build:section lists the versioned image and the floating${CACHE_TAG:-cache}_...tag intags:andcache_from:— skip this and CI builds of your service are always cold (see the cache section inAGENTS.md). -
Build and verify:
Be honest about what you get: CI covers only the desktop x86 simulation path (Supported Platforms — what CI covers). No CI job builds or runs l4t, voxl, or your new profile — its status is whatever you verify on hardware. Add a row to the platform matrix stating exactly that.