Project AirSim ROS2 C++ Bridge
Project AirSim provides a ROS2 C++ bridge package named
projectairsim_ros2_cpp. The package is located under:
ros/projectairsim_ros2_cpp
The bridge is an rclcpp node that connects to Project AirSim through the
Project AirSim C++ client API, discovers simulator topics, republishes supported
payloads as typed ROS2 messages, and exposes command services/actions.
Requirements
The bridge has been built and tested with ROS2 Humble. Source your ROS2 installation before building or running the node:
source /opt/ros/humble/setup.bash
The package depends on:
Dependency |
Used for |
|---|---|
|
ROS2 CMake package build |
|
Custom service/action generation |
|
ROS2 C++ node |
|
|
|
poses and path goals |
|
occupancy grid service responses |
|
simulation clock topic |
|
sensor topics |
|
topic info and raw JSON fallback topics |
|
TF broadcasting |
Build
From the repository root:
source /opt/ros/humble/setup.bash
cd ros
colcon build --packages-select projectairsim_ros2_cpp
source install/setup.bash
Test
The ROS2 C++ package includes C++ contract tests for the generated topics, services, and action interfaces.
source /opt/ros/humble/setup.bash
cd ros
colcon test --packages-select projectairsim_ros2_cpp
colcon test-result --all --verbose
Run
Start Project AirSim first. Then, from ros:
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 run projectairsim_ros2_cpp projectairsim_ros2_cpp_node --ros-args \
-p scene_config:=scene_drone_sensors.jsonc \
-p sim_config_path:=../client/python/example_user_scripts/sim_config
If Project AirSim is not reachable at 127.0.0.1, set address to the host
running the simulator:
PROJECTAIRSIM_ROOT="$(git rev-parse --show-toplevel)"
ros2 run projectairsim_ros2_cpp projectairsim_ros2_cpp_node --ros-args \
-p address:=<projectairsim_host_ip> \
-p scene_config:=scene_drone_sensors.jsonc \
-p sim_config_path:=../client/python/example_user_scripts/sim_config
If a scene is already loaded and should not be changed by the bridge, omit
scene_config. In that mode the node connects to the existing simulator scene,
adopts the discovered scene topic root, and starts topic and clock publishing
when Project AirSim reports topic information.
Parameters
Parameter |
Default |
Description |
|---|---|---|
|
|
Project AirSim server address. Set this when the simulator runs on another host or network namespace. |
|
|
Project AirSim pub-sub topic port. |
|
|
Project AirSim service request port. |
|
empty |
Scene config file to load at startup. Relative names are resolved against |
|
|
Directory containing scene, robot, and environment config files. |
|
|
Delay after loading a scene before topic discovery. |
|
|
Native Project AirSim topic root to rewrite. |
|
|
ROS topic root used when publishing bridged topics. |
|
|
Publish unsupported Project AirSim topics as |
|
|
Broadcast TF transforms from |
|
|
Parent frame used for TF transforms. |
|
|
Periodic topic discovery interval. |
|
|
Vehicle used for single-drone services/actions. |
|
|
Root namespace for command services and actions. |
|
|
|
Topics
List ROS2 topics:
ros2 topic list -t
The bridge subscribes to native Project AirSim topics such as /Sim/... and republishes them under /ProjectAirsim/... by default.
Robot state topics use the vehicle name as frame_id; camera image and
camera-info topics use a unique sanitized camera frame so they can participate
in TF without colliding with the vehicle frame.
Implemented typed topic conversions:
Project AirSim topic suffix |
ROS2 topic suffix |
ROS2 topic type |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
camera topics |
same suffix |
|
|
|
|
The bridge also publishes:
Topic |
Type |
Description |
|---|---|---|
|
|
JSON list of Project AirSim topic paths from the first discovery pass after startup or scene load. |
|
|
Native Project AirSim scene-clock topic, republished without service polling. |
|
|
Vehicle and camera transforms when |
To echo a topic:
ros2 topic echo /ProjectAirsim/<scene_name>/robots/<vehicle_name>/actual_pose
If you want raw JSON fallback topics for unsupported Project AirSim topics:
ros2 run projectairsim_ros2_cpp projectairsim_ros2_cpp_node --ros-args \
-p publish_unmatched_as_json:=true
Services
List services:
ros2 service list -t
Single-vehicle services use the configured vehicle_name, default Drone1:
Service |
Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Global, group, and scene services:
Service |
Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Examples:
ros2 service call /projectairsim/Drone1/arm projectairsim_ros2_cpp/srv/Arm \
"{wait_on_last_task: true}"
ros2 service call /projectairsim/Drone1/takeoff projectairsim_ros2_cpp/srv/Takeoff \
"{wait_on_last_task: true}"
ros2 service call /projectairsim/Drone1/move_to_position projectairsim_ros2_cpp/srv/MoveToPosition \
"{x: 5.0, y: 0.0, z: -3.0, velocity: 2.0, timeout_sec: 10.0, drive_train_type: 0, yaw_is_rate: true, yaw: 0.0, lookahead: -1.0, adaptive_lookahead: 1.0, wait_on_last_task: true}"
ros2 service call /projectairsim/Drone1/land projectairsim_ros2_cpp/srv/Land \
"{wait_on_last_task: true}"
For a native wheeled vehicle, start the bridge with
vehicle_name:=WheeledVehicle. The typed service keeps the same indexed
interface (0 throttle, 1 steering, 2 brake). On the first indexed control
request for each robot, the bridge queries
/Sim/<scene>/robots/<robot>/GetRobotType with no arguments. Robot names are
arbitrary: wheeled-vehicle maps to SetThrottle, SetSteering, and SetBrakes;
unreal-vehicle forwards SetParameter unchanged, preserving Blueprint indices.
drone, jsbsim, and other reject this indexed control service; existing
flight services are unchanged. Invalid wheeled indices, failed/missing RPCs,
malformed results, and unknown types return an explicit error without sending a
control RPC. There is no name-based fallback. An empty requested name uses the
configured vehicle_name. Python/C++ WheeledVehicle clients use named controls.
The server classifies validated vehicle-class fields first, then JSBSim physics
as jsbsim, FastPhysics as drone, and otherwise other, independently of the
controller. This identifies the configured backend, not spawn/drive readiness.
Valid results are cached by full scene/robot path until the bridge reconnects
or successfully loads a scene; failed lookups are retried, not cached. Older
simulators without GetRobotType cannot use this indexed routing service.
To attach without reloading, leave scene_config empty (its default). World
discovers the scene and robot names from topics without fetching configuration
or resetting the scene. No backend launch parameter or extra JSONC mapping is
needed. External scene changes are not adopted automatically: reconnect the
bridge or load the scene through its own load service with
is_primary_client: true.
The native topic channel is single-consumer (PAIR0). When ROS owns it, connect
Python diagnostics with client.connect_services() rather than connect();
otherwise the competing topic connection can prevent clock and TF delivery.
Example:
ros2 service call /projectairsim/WheeledVehicle/set_parameter \
projectairsim_ros2_cpp/srv/SetParameter "{index: 0, value: 0.7}"
See Native Wheeled Vehicles for the complete setup and SimpleDrive workflow.
Reload a scene at runtime:
ros2 service call /projectairsim/load_scene projectairsim_ros2_cpp/srv/LoadScene \
'{scene_file: "scene_drone_sensors.jsonc", is_primary_client: true}'
Use is_primary_client: true when this bridge should ask Project AirSim to load
the scene. Use is_primary_client: false when another client has already loaded
the scene and this bridge should attach to it.
Create a voxel grid file:
ros2 service call /projectairsim/create_voxel_grid projectairsim_ros2_cpp/srv/CreateVoxelGrid \
'{position_x: 0.0, position_y: 0.0, position_z: -4.0, ncells_x: 20, ncells_y: 20, ncells_z: 10, resolution: 1.0, n_z_resolution: 10, output_file: "/tmp/projectairsim_voxel_grid.binvox"}'
Return a ROS2 occupancy grid. The 3D voxel grid is projected into a 2D
nav_msgs/OccupancyGrid; a cell is occupied if any voxel in its Z column is
occupied.
ros2 service call /projectairsim/occupancy_grid projectairsim_ros2_cpp/srv/OccupancyGrid \
"{position_x: 0.0, position_y: 0.0, position_z: -4.0, ncells_x: 20, ncells_y: 20, ncells_z: 10, res: 1.0, n_z_resolution: 10}"
Get simulation time in nanoseconds:
ros2 service call /projectairsim/get_clock projectairsim_ros2_cpp/srv/GetClock "{}"
Get the scene origin geo point:
ros2 service call /projectairsim/get_origin_geo_point projectairsim_ros2_cpp/srv/GetOriginGeoPoint "{}"
Call any Project AirSim C++ client request-backed API by method path and JSON parameters:
ros2 service call /projectairsim/request projectairsim_ros2_cpp/srv/RawRequest \
'{method: "/Sim/GetBuildCommitHash", json_parameters: "{}"}'
/projectairsim/request is a generic escape hatch for Project AirSim server
request paths. Prefer typed ROS2 services when one exists. For generic calls,
use a method path and JSON payload supported by the Project AirSim server.
Actions
The bridge provides a MoveOnPath action:
Action |
Type |
|---|---|
|
|
List actions:
ros2 action list -t
Send a path goal:
ros2 action send_goal /projectairsim/Drone1/move_on_path projectairsim_ros2_cpp/action/MoveOnPath \
"{path: [{pose: {position: {x: 5.0, y: 0.0, z: -3.0}, orientation: {w: 1.0}}}, {pose: {position: {x: 5.0, y: 5.0, z: -3.0}, orientation: {w: 1.0}}}], velocity: 2.0, timeout_sec: 20.0, lookahead: -1.0, adaptive_lookahead: 1.0, drive_train_type: 0, yaw_is_rate: true, yaw: 0.0, wait_on_last_task: true}"
Common Workflow
Use three terminals. Run the ROS2 commands from the repository root unless noted otherwise:
Start Project AirSim.
Start the bridge:
source /opt/ros/humble/setup.bash
cd ros
source install/setup.bash
ros2 run projectairsim_ros2_cpp projectairsim_ros2_cpp_node --ros-args \
-p scene_config:=scene_drone_sensors.jsonc \
-p sim_config_path:=../client/python/example_user_scripts/sim_config
Inspect and command from ROS2:
source /opt/ros/humble/setup.bash
cd ros
source install/setup.bash
ros2 topic list -t
ros2 service list -t
ros2 action list -t
Then call services such as arm, takeoff, move, and land.
Resolved Limitations And Remaining Gaps
The ROS2 C++ bridge provides typed ROS2 coverage for the high-priority areas below:
Area |
Status |
|---|---|
Radar typed messages |
Implemented as |
Camera info topics |
Implemented as |
TF broadcasting |
Implemented for vehicle pose and camera pose payloads; controlled by |
Segmentation and object material/texture services |
Implemented as typed ROS2 services. |
Remaining gaps are narrower typed wrappers for Project AirSim request paths that
currently require /projectairsim/request, plus richer typed conversions for
specialized sensor/status payloads that do not yet have dedicated ROS message
contracts.
Copyright (C) IAMAI CONSULTING CORP
MIT License. All rights reserved.