claudeDroneteam-docs
documentation · reference
Docs reference

Structured knowledge from collected_doc_media/claudedrone_docs/. Browse the tree on the left; the source of truth is markdown in the repo.

Nav2 for an aerial drone — configuration and aerial-specific gotchas (research)

Nav2 1.0 on ROS 2 Jazzy for an indoor drone: NavigateToPose from mission_fsm, 2D planner at flight altitude, the REP 105 / TwistStamped / Costmap gotchas.

stableresearchbestupdated 2026-05-11T00:00:00.000ZClaudeDroneResearchNav2DroneAerialPlanner

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_link frame.
  • ArduPilot ENU FLU — forward = +X, left = +Y, up = +Z in 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)

  1. ros2 topic echo /navigate_to_pose/_action/feedback shows feedback during the flight.
  2. rviz2 → load /map + /next_frontier marker + Nav2 path — visible path to the frontier.
  3. Drone reaches the goal with a 0.3 m tolerance.
  4. After SUCCEEDED → mission_fsm transitions to SCAN.

Sources

  1. Nav2 docs — https://docs.nav2.org
  2. GSoC 2023 ArduPilot ROS 2 — https://discuss.ardupilot.org/t/.../101121 — REP 105 / TwistStamped / Costmap lessons
  3. ArduPilot ros-slam.html — drone-specific TF-tree pattern
  4. DaniGarciaLopez/ros2_explorer — reference architecture with Cartographer + Nav2 + WFD
© 2026 claudeDrone Team · auto-pipeline · Nuxt 3 SSR