Context
Nav2 is ground-robot-first by design. Using it on a drone requires adaptation: 2D planning at a fixed flight altitude, a specific TF tree, and integration through the NavigateToPose action from mission_fsm.
The non-obvious framing is that Nav2 doesn’t care that the robot flies, as long as you give it the right TF tree and a 2D costmap. The wings (so to speak) are abstracted away — Nav2 produces a 2D path, you fly it at constant altitude. The gotchas mostly come from the parts where ground-robot assumptions leak through.
Pattern: 2D Nav2 at flight altitude
The drone is in 3D, but the SLAM is 2D (a plane at takeoff altitude). frontier_picker publishes /next_frontier as a PoseStamped with z = takeoff_alt. The Nav2 controller computes the path in that plane.
TF tree (ArduPilot ros-slam.html pattern)
map → odom → base_link → laser_frame
↓
base_footprint == base_link (drone 2D plane)
static_transform_publisher 0 0 0 0 0 0 base_link laser_frame — laser at the drone’s center of mass / servo axis vertical.
odom source = MAVROS /mavros/local_position/odom (IMU dead-reckoning + EKF).
Nav2 configuration
controller_server:
ros__parameters:
controller_frequency: 10.0
controller_plugins: ["FollowPath"]
FollowPath:
plugin: "nav2_regulated_pure_pursuit_controller::RegulatedPurePursuitController"
max_linear_vel: 0.5 # indoor — slow
lookahead_dist: 0.5
planner_server:
ros__parameters:
planner_plugins: ["GridBased"]
GridBased:
plugin: "nav2_navfn_planner/NavfnPlanner"
tolerance: 0.3
use_astar: true
bt_navigator:
ros__parameters:
default_nav_to_pose_bt_xml: $(find-pkg-share nav2_bt_navigator)/.../navigate_to_pose_w_replanning_and_recovery.xml
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
resolution: 0.05
robot_radius: 0.3 # iris ~30 cm diameter
plugins: ["voxel_layer", "inflation_layer"]
voxel_layer:
observation_sources: scan
scan:
topic: /scan # remap from /drone/sweep/result
obstacle_max_range: 8.0 # TF-Luna max
raytrace_max_range: 8.0
inflation_layer:
inflation_radius: 0.5
Gotchas from GSoC 2023 ArduPilot ROS 2
REP 105 TF violations
ros_gz transforms violate REP 105 (TF transformation conventions). Fix: use robot_state_publisher to publish a correct TF tree from URDF — not ros_gz_bridge for static frames.
Twist vs TwistStamped
Nav2 uses Twist (no header). ArduPilot’s MAVROS cmd_vel_unstamped accepts Twist. Compatible. But watch the body-frame convention:
- Nav2 controller computes in
base_linkframe. - ArduPilot ENU FLU —
forward = +X, left = +Y, up = +Zin body frame. - ✅ Matches Nav2 convention.
Cartographer–Costmap incompatibility
Was a GSoC 2023 issue with Cartographer. slam_toolbox is Nav2-native — the issue doesn’t reproduce.
NavigateToPose ActionClient (from mission_fsm)
See Frontier exploration alternatives for the full pattern. In brief:
goal_msg = NavigateToPose.Goal()
goal_msg.pose = next_frontier_pose # PoseStamped from frontier_picker
goal_msg.pose.pose.position.z = TAKEOFF_ALT # lock flight altitude
self.nav_client.wait_for_server(timeout_sec=2.0)
future = self.nav_client.send_goal_async(
goal_msg, feedback_callback=self._nav_feedback)
future.add_done_callback(self._goal_response_cb)
Recovery behaviors
The default BT XML includes a recovery subtree: BackUp → Spin → Wait. For a drone, Spin is dangerous in a narrow indoor corridor — consider a custom BT.
Workaround: point bt_navigator’s default_nav_to_pose_bt_xml at a custom XML without Spin:
<RecoveryFallback name="RecoveryFallback">
<RoundRobin name="RecoveryActions">
<Wait wait_duration="5.0"/>
<!-- No Spin / BackUp — too risky indoor -->
</RoundRobin>
</RecoveryFallback>
Apt install
sudo apt install ros-jazzy-navigation2 ros-jazzy-nav2-bringup
Smoke acceptance (after bring-up)
ros2 topic echo /navigate_to_pose/_action/feedbackshows feedback during the flight.rviz2→ load/map+/next_frontiermarker + Nav2 path — visible path to the frontier.- Drone reaches the goal with a 0.3 m tolerance.
- After SUCCEEDED →
mission_fsmtransitions to SCAN.
Sources
- Nav2 docs — https://docs.nav2.org
- GSoC 2023 ArduPilot ROS 2 — https://discuss.ardupilot.org/t/.../101121 — REP 105 / TwistStamped / Costmap lessons
- ArduPilot ros-slam.html — drone-specific TF-tree pattern
- DaniGarciaLopez/ros2_explorer — reference architecture with Cartographer + Nav2 + WFD