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.
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.bashThe package depends on:
| Dependency | Used for |
|---|---|
ament_cmake |
ROS2 CMake package build |
rosidl_default_generators |
Custom service/action generation |
rclcpp |
ROS2 C++ node |
rclcpp_action |
MoveOnPath action server |
geometry_msgs |
poses and path goals |
nav_msgs |
occupancy grid service responses |
rosgraph_msgs |
simulation clock topic |
sensor_msgs |
sensor topics |
std_msgs |
topic info and raw JSON fallback topics |
tf2_ros |
TF broadcasting |
From the repository root:
source /opt/ros/humble/setup.bash
cd ros
colcon build --packages-select projectairsim_ros2_cpp
source install/setup.bashThe 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 --verboseStart 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_configIf 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_configIf 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.
| Parameter | Default | Description |
|---|---|---|
address |
127.0.0.1 |
Project AirSim server address. Set this when the simulator runs on another host or network namespace. |
port_topics |
8989 |
Project AirSim pub-sub topic port. |
port_services |
8990 |
Project AirSim service request port. |
scene_config |
empty | Scene config file to load at startup. Relative names are resolved against sim_config_path. When empty, the bridge connects to the currently loaded scene. |
sim_config_path |
client/python/example_user_scripts/sim_config |
Directory containing scene, robot, and environment config files. |
delay_after_load_sec |
2.0 |
Delay after loading a scene before topic discovery. |
projectairsim_topic_root |
/Sim |
Native Project AirSim topic root to rewrite. |
ros_topic_root |
/ProjectAirsim |
ROS topic root used when publishing bridged topics. |
publish_unmatched_as_json |
false |
Publish unsupported Project AirSim topics as std_msgs/String JSON. |
publish_tf |
true |
Broadcast TF transforms from /actual_pose and camera pose payloads. |
tf_world_frame_id |
map |
Parent frame used for TF transforms. |
refresh_topics_period_sec |
0.0 |
Periodic topic discovery interval. 0.0 disables polling; use a positive value only if scenes/topics can change outside this node. |
vehicle_name |
Drone1 |
Vehicle used for single-drone services/actions. |
service_root |
/projectairsim |
Root namespace for command services and actions. |
image_qos_depth |
5 |
KEEP_LAST depth for image publishers. Values below 1 are rejected. |
A raw 3840×2160 BGR image is about 24 MiB, which can exceed the practical defaults of DDS shared-memory transports. For local 4K image subscribers, use the installed Fast DDS profile that provides an explicit 256 MiB shared-memory segment:
source /opt/ros/humble/setup.bash
source ros/install/setup.bash
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export FASTDDS_DEFAULT_PROFILES_FILE="$(ros2 pkg prefix projectairsim_ros2_cpp)/share/projectairsim_ros2_cpp/config/fastdds_shm_256m.xml"
export FASTRTPS_DEFAULT_PROFILES_FILE="$FASTDDS_DEFAULT_PROFILES_FILE"Set this environment before starting the Project AirSim ROS2 bridge and every ROS2 image subscriber. All participants must use the same DDS domain and Fast DDS profile.
This profile configures only the Fast DDS shared-memory transport and sets
useBuiltinTransports to false. Consequently:
- UDP is disabled and no TCP transport descriptor is configured. DDS participants cannot communicate across machines through this profile.
- There is no UDP or TCP fallback. If shared-memory initialization or access fails, ROS discovery and delivery fail instead of switching to a network transport.
- Every intended participant, including command-line tools, must use a compatible Fast DDS environment. Mixed RMW implementations or profiles can result in missing discovery or large images failing while smaller messages appear to work.
- Containers must share an IPC namespace and compatible shared-memory permissions. Independent default IPC namespaces cannot use this transport to communicate.
- Fast DDS creates shared-memory resources for its participants. Multiple
processes, concurrent 4K streams, and larger QoS histories increase
/dev/shmpressure; abnormal termination can temporarily leave stale Fast DDS files or locks. - DDS shared memory avoids sending image data over the network, but it does not eliminate ROS serialization, bridge-side image construction, or every payload copy. This profile is not end-to-end zero-copy.
Use this profile only when the bridge and all required subscribers are on the same host. Configure a UDP or TCP transport instead when remote DDS communication is required.
The host must have enough free /dev/shm capacity for the 256 MiB segment and
Fast DDS bookkeeping. When processes run in containers, put them in the same
IPC namespace (for example, --ipc=host) or otherwise provide a shared IPC
namespace with sufficient shared-memory capacity.
The bridge publishes images with KEEP_LAST history. Its default
image_qos_depth is 5; reduce it only when lower latency and dropping stale
frames is preferable to buffering bursts:
ros2 run projectairsim_ros2_cpp projectairsim_ros2_cpp_node --ros-args \
-p image_qos_depth:=1To return to normal Fast DDS transport discovery, unset both profile variables before starting new ROS2 processes:
unset FASTDDS_DEFAULT_PROFILES_FILE
unset FASTRTPS_DEFAULT_PROFILES_FILEThe simulator publishes /Sim/<scene>/clock from the scene tick. The bridge
subscribes to that native clock stream and republishes every sample on ROS
/clock; it does not poll GetSimTime. Sensor message headers likewise retain
their native per-sample time_stamp values. If a sensor payload has no valid
time_stamp, the bridge warns and publishes the conventional
invalid/uninitialized ROS timestamp (sec: 0, nanosec: 0) instead of
substituting the bridge's current time. ROS time fields are integers, so they
cannot represent NaN.
For optimized native image messages, an absent time_stamp or an unsigned
value larger than INT64_MAX produces the same zero timestamp while preserving
the image. A present time_stamp encoded with the wrong MessagePack type
(such as a string, floating-point value, Boolean, or null), or encoded as a
negative integer, makes the image payload malformed; the bridge warns and
drops that image instead of substituting ROS node time.
List ROS2 topics:
ros2 topic list -tThe 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 |
|---|---|---|
/actual_pose |
/actual_pose |
geometry_msgs/msg/PoseStamped |
/gps |
/gps |
sensor_msgs/msg/NavSatFix |
/imu, /imu_kinematics |
/imu |
sensor_msgs/msg/Imu |
/barometer |
/barometer |
sensor_msgs/msg/FluidPressure |
/magnetometer |
/magnetometer |
sensor_msgs/msg/MagneticField |
/lidar |
/lidar |
sensor_msgs/msg/PointCloud2 |
/radar_detections |
/radar_detections |
projectairsim_ros2_cpp/msg/RadarScan |
/radar_tracks |
/radar_tracks |
projectairsim_ros2_cpp/msg/RadarTracks |
| camera topics | same suffix | sensor_msgs/msg/Image |
*_camera_info |
<camera_topic>/camera_info |
sensor_msgs/msg/CameraInfo |
The bridge also publishes:
| Topic | Type | Description |
|---|---|---|
/projectairsim/topic_info |
std_msgs/msg/String |
JSON list of Project AirSim topic paths from the first discovery pass after startup or scene load. |
/clock |
rosgraph_msgs/msg/Clock |
Native Project AirSim scene-clock topic, republished without service polling. |
/tf |
tf2_msgs/msg/TFMessage |
Vehicle and camera transforms when publish_tf=true. |
To echo a topic:
ros2 topic echo /ProjectAirsim/<scene_name>/robots/<vehicle_name>/actual_poseIf 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:=trueList services:
ros2 service list -tSingle-vehicle services use the configured vehicle_name, default Drone1:
| Service | Type |
|---|---|
/projectairsim/Drone1/takeoff |
projectairsim_ros2_cpp/srv/Takeoff |
/projectairsim/Drone1/land |
projectairsim_ros2_cpp/srv/Land |
/projectairsim/Drone1/arm |
projectairsim_ros2_cpp/srv/Arm |
/projectairsim/Drone1/disarm |
projectairsim_ros2_cpp/srv/Disarm |
/projectairsim/Drone1/move_to_position |
projectairsim_ros2_cpp/srv/MoveToPosition |
/projectairsim/Drone1/move_on_path |
projectairsim_ros2_cpp/srv/MoveOnPath |
Global, group, and scene services:
| Service | Type |
|---|---|
/projectairsim/request |
projectairsim_ros2_cpp/srv/RawRequest |
/projectairsim/get_client_info |
projectairsim_ros2_cpp/srv/GetClientInfo |
/projectairsim/get_topic_info |
projectairsim_ros2_cpp/srv/GetTopicInfo |
/projectairsim/get_drones |
projectairsim_ros2_cpp/srv/GetDrones |
/projectairsim/publish |
projectairsim_ros2_cpp/srv/Publish |
/projectairsim/unsubscribe |
projectairsim_ros2_cpp/srv/Unsubscribe |
/projectairsim/cancel_all_requests |
projectairsim_ros2_cpp/srv/CancelAllRequests |
/projectairsim/takeoff_group |
projectairsim_ros2_cpp/srv/TakeoffGroup |
/projectairsim/land_group |
projectairsim_ros2_cpp/srv/LandGroup |
/projectairsim/arm_group |
projectairsim_ros2_cpp/srv/ArmGroup |
/projectairsim/disarm_group |
projectairsim_ros2_cpp/srv/DisarmGroup |
/projectairsim/reset |
projectairsim_ros2_cpp/srv/Reset |
/projectairsim/load_scene |
projectairsim_ros2_cpp/srv/LoadScene |
/projectairsim/create_voxel_grid |
projectairsim_ros2_cpp/srv/CreateVoxelGrid |
/projectairsim/occupancy_grid |
projectairsim_ros2_cpp/srv/OccupancyGrid |
/projectairsim/get_clock |
projectairsim_ros2_cpp/srv/GetClock |
/projectairsim/get_origin_geo_point |
projectairsim_ros2_cpp/srv/GetOriginGeoPoint |
/projectairsim/set_segmentation_id_by_name |
projectairsim_ros2_cpp/srv/SetSegmentationIDByName |
/projectairsim/get_segmentation_id_by_name |
projectairsim_ros2_cpp/srv/GetSegmentationIDByName |
/projectairsim/get_segmentation_id_map |
projectairsim_ros2_cpp/srv/GetSegmentationIDMap |
/projectairsim/set_object_material |
projectairsim_ros2_cpp/srv/SetObjectMaterial |
/projectairsim/set_object_texture |
projectairsim_ros2_cpp/srv/SetObjectTexture |
/projectairsim/swap_object_texture |
projectairsim_ros2_cpp/srv/SwapObjectTexture |
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}"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.
The bridge provides a MoveOnPath action:
| Action | Type |
|---|---|
/projectairsim/Drone1/move_on_path |
projectairsim_ros2_cpp/action/MoveOnPath |
List actions:
ros2 action list -tSend 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}"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 -tThen call services such as arm, takeoff, move, and land.
The ROS2 C++ bridge provides typed ROS2 coverage for the high-priority areas below:
| Area | Status |
|---|---|
| Radar typed messages | Implemented as projectairsim_ros2_cpp/msg/RadarScan and RadarTracks. |
| Camera info topics | Implemented as <camera_topic>/camera_info with sensor_msgs/msg/CameraInfo. |
| TF broadcasting | Implemented for vehicle pose and camera pose payloads; controlled by publish_tf. |
| 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.