Skip to main content
Version: 9

Navigate with Nav2 in hangar_sim

The hangar_sim robot configuration features a Clearpath Ridgeback mobile base carrying a UR5e arm. This guide covers the full mobile navigation stack: how the lidar feeds into Nav2's costmaps, how the robot localizes itself, how velocity commands reach the mecanum wheels, and how to use the navigation Objectives.

Adapting this to your own robot?

See Mobile Base Control Setup for configuring whole-body MoveIt planning with a mobile base, and Mobile Navigation Setup for wiring Nav2 into a new robot configuration package.

Launch MoveIt Pro

We assume you have already installed MoveIt Pro to the default install location. Launch the application using:

moveit_pro run -c hangar_sim

The Robot: Ridgeback Mecanum Base

Lidar Sensors

The robot is equipped with two SICK TIM571 planar lidars: one at the front bumper and one at the rear bumper.

Key characteristics:

  • Each lidar is a 91-beam sensor covering a 270° field of view
  • Front lidar publishes raw scans to /scan_front; rear lidar publishes to /scan_rear, both with best-effort QoS
  • Each raw scan passes through an independent angular bounds filter (keeping scan angles 45°–225°, i.e., 0.7854–3.9270 rad) that clips chassis self-hits
  • Filtered scans are published on /scan_front_filtered and /scan_rear_filtered
  • Combined coverage: 360° around the robot (each lidar contributes a clean 180° arc)

Both filtered topics are the obstacle sources for both costmaps.

Localization

By default, hangar_sim passes MuJoCo's ground-truth odometry directly to Nav2 via the /odom topic. This works well in simulation but does not reflect what a real robot would experience.

warning

Using odometry-only localization on a real robot causes the position estimate to drift over time due to wheel slip and IMU bias. Over medium to long distances this drift accumulates, causing the costmap and planned paths to diverge from the robot's actual position. For real-robot deployments, use a localization system.

hangar_sim ships with an optional Fuse state estimator that fuses two sensor sources for a more realistic odometry estimate:

  • Wheel odometry from /platform_velocity_controller_nav2/odom — provides x/y position and yaw, fused differentially to handle drift
  • IMU from /imu_sensor_broadcaster/imu_reliable — provides absolute orientation (roll, pitch, yaw) and yaw rate, anchoring the estimate against wheel slip

The filtered estimate is published on /odom_filtered. To enable it, set use_fuse to true in robot_drivers_to_persist_sim.launch.py and update odom_topic in nav2_params.yaml from /odom to /odom_filtered.

The video below shows Fuse in action during straight, spin, strafe, and straight maneuvers. The green arrow is raw wheel odometry (drifting), the red arrow is ground truth from MuJoCo, and the yellow path is the Fuse-filtered estimate tracking the ground truth closely.

Costmaps

Nav2 maintains two costmaps simultaneously. Both read obstacle data from /scan_front_filtered and /scan_rear_filtered as independent observation sources.

Global Costmap

The global costmap covers the full hangar map and is used for high-level path planning.

PropertyValue
Framemap
Resolution5 cm/cell
Update frequency1 Hz
LayersStatic (pre-built map) + Obstacle (front + rear lidar, 8.0 m max range) + Inflation (0.7 m radius)

The static layer loads the pre-built occupancy grid of the hangar. The obstacle layer adds dynamic obstacles from both lidars, up to 8.0 m range (with 10.0 m raytrace for clearing). The inflation layer expands all obstacles by 0.7 m, approximately the robot radius plus padding, to ensure the robot body clears obstacles while traversing.

Why limit obstacle range to 8.0 m when the lidar reaches farther?

A few reasons drive this choice:

  • Long-range lidar returns tend to be noisier, so capping the obstacle range filters out unreliable readings at the edges of the sensor's range.
  • Restricting the range limits how much of the hangar scene is visible to the costmap, keeping costmap updates more focused and easier to observe in tutorials.
  • The raytrace range (10.0 m) is intentionally set longer than the obstacle range (8.0 m). This avoids a common edge case where a beam that just misses a real obstacle fails to clear a previously marked cell, leaving a phantom obstacle stuck in the costmap.

Local Costmap

The local costmap is a 5 × 5 m rolling window centered on the robot, used for reactive obstacle avoidance during path following. In hangar_sim, the lidar is primarily a long-range planar sensor suited for localization; the local costmap here demonstrates the concept but does not include the short-range sensors (such as depth cameras) that would typically drive its size in a production system.

PropertyValue
Frameodom
Size5 × 5 m rolling window
Resolution5 cm/cell
Update frequency10 Hz
LayersObstacle (front + rear lidar, 4.5 m raytrace range, 3.5 m obstacle range) + Inflation (0.75 m radius)

The local costmap updates at 10 Hz (much faster than the global costmap) and marks obstacles up to 3.5 m away, with a 4.5 m raytrace range for clearing. It does not include a static layer; the robot's immediate surroundings are treated as fully dynamic.

Costmaps in the MoveIt Pro UI

Both costmaps are visible in the Visualization view as colored overlays in the 3D scene. The UI subscribes to the costmap topics published by Nav2 and updates in real time as the robot moves and new obstacles are detected.

To enable the costmap overlays, open the View menu and toggle Global Costmap and/or Local Costmap under the Navigation section.

The RViz view below illustrates the 5×5 m rolling window size — the white square is the local costmap centered on the robot, surrounded by the black global costmap.

The side-by-side below shows the MoveIt Pro UI (left) with the purple inflation overlay around detected obstacles, alongside RViz (right) displaying the local costmap updating over the black global costmap with white lidar hit points from both the front and rear sensors.

Controller Pipeline

At startup, joint_trajectory_controller (whole-body) is the active controller. When a navigation Objective runs, the SwitchController Behavior deactivates it and activates platform_velocity_controller_nav2. The Nav2 command pipeline then works as follows:

  1. MPPI controller (FollowPath) — runs at 20 Hz, samples trajectory candidates and selects the one with the lowest cost against the costmaps
  2. Velocity smoother — smooths the MPPI output to respect acceleration limits (max ±2.5 m/s²)
  3. /cmd_vel relay — forwards /cmd_vel to /platform_velocity_controller_nav2/cmd_vel_unstamped
  4. platform_velocity_controller_nav2 — a clearpath_mecanum_drive_controller controller that converts the body-frame velocity command to individual wheel speeds for the four mecanum wheels
note

Running a whole-body arm Objective (for example, Teleoperate) switches back to joint_trajectory_controller, displacing platform_velocity_controller_nav2. Navigation Objectives re-activate it via SwitchController at the start of each run.

The simplest navigation Objective. Select it from the Objectives panel to get started.

It then:

  1. Computes a single global path to the user-clicked goal using ComputePathToPoseAction
  2. Displays the planned path and waits for user confirmation with WaitForUserPathApproval
  3. Follows the approved path using FollowPathAction

If an obstacle appears during execution, the robot may get stuck — there is no replanning, and the Objective will keep running until you click Stop Objective.

For dynamic environments where obstacles may appear during navigation, use Navigate to Clicked Point with Replanning. This Objective delegates full navigation control to Nav2, which replans the path continuously as the local costmap updates.

See Navigate to a Goal with Replanning for a detailed walkthrough of how this Objective works, how Nav2's replanning Behavior Tree handles recovery, and when you may need to stop the Objective manually.