RobotForge
Published·~18 min

Nav2: the mobile navigation stack explained

Costmaps, planners, controllers, behavior trees, recovery — the full Nav2 stack mapped one box at a time. Configure it once and most mobile-robot navigation works out of the box.

by RobotForge
#ros2#nav2#navigation

Nav2 is the navigation stack that ships with ROS 2 — and the navigation stack that runs on most production indoor robots in 2026. It's a set of plug-in modular components: costmaps, global planner, local planner (controller), behavior tree, recovery behaviors. Configured well, it gets a robot from point A to point B around obstacles. Configured badly, the robot drives into walls. Here's the architecture and the practical knobs.

The architecture

bt_navigator planner_server (NavFn, Smac) controller_server (DWB, MPPI, RPP) behavior_server (recoveries) global_costmap local_costmap map (SLAM or static) /scan, /pointcloud (sensors) /cmd_vel →
Nav2's main pieces: a behavior tree at the top orchestrates a planner (slow, global) + a controller (fast, local) + recoveries when something goes wrong.

The pieces, one at a time

bt_navigator — the orchestrator

Top-level node. Runs a behavior tree (XML) that says: "compute a global path, follow it with the controller, if stuck try recovery, retry, etc." The default BT works for most cases; you customize for app-specific needs (e.g. wait for a door to open).

planner_server — the global path

Given (start, goal) and the global costmap, plan a free-space path. Several plug-in algorithms:

  • NavFn: Dijkstra on the costmap. Fast, simple, the default.
  • Smac Planner: hybrid A* (kinematically aware), SE(2) variants for car-like robots, 3D for arbitrary motion. Slower but better paths for non-holonomic vehicles.
  • Theta*: any-angle planner; produces straighter paths than NavFn at similar cost.

controller_server — the local control

Takes the global path and the local costmap, computes cmd_vel at high rate (10–20 Hz) to follow the path while reacting to live obstacles. Plug-ins:

  • DWB (Dynamic Window Approach): the legacy default. Samples velocities, scores them, picks the best. Works fine but plain.
  • RPP (Regulated Pure Pursuit): newer default. Look-ahead point on the path; steer toward it; slow down on curvature and obstacles. Simple and tunable.
  • MPPI (Model Predictive Path Integral): GPU-friendly sampling-based MPC. Production-grade, used by warehouse robots in 2026.
  • TEB (Timed Elastic Band): trajectory optimizer that smooths the path while honoring kinematics. Also production-grade.

Pick by robot kinematics: differential drive → RPP or DWB; Ackermann → Smac + RPP/MPPI; omnidirectional → DWB or MPPI.

Costmaps — where can the robot drive

The costmap is a 2D occupancy grid where each cell has a cost: 0 = free, 254 = lethal (obstacle), values in between = "near obstacle, prefer not to be here." Two layers, both produced from a stack of sub-layers:

  • global_costmap: low-rate, full-map; updated on map changes. Used by the global planner.
  • local_costmap: high-rate, small rolling window centered on the robot; updated by live sensor data. Used by the controller.

Costmap sub-layers:

  • StaticLayer: from the saved map. Walls, doors, furniture.
  • ObstacleLayer / VoxelLayer: from live sensor data (lidar, depth camera).
  • InflationLayer: a buffer of "high cost" around obstacles. Keeps the path off walls.
  • SpeedFilter / KeepoutFilter: domain rules — speed limits in zones, no-go regions.

behavior_server — recoveries

What happens when the robot is stuck. Default behaviors: spin in place (clear local costmap), back up, wait, abort. The behavior tree decides when to call them.

The minimum config

You need three files:

  1. nav2_params.yaml — every Nav2 node's parameters. Hundreds of fields. Start from the official template; tune piecemeal.
  2. map.yaml + map.pgm — your saved map (SLAM toolbox saves both).
  3. launch file — bring up Nav2 with your params and map.

The official Nav2 docs ship a ready-to-go template. Copy it, change the parameters relevant to your robot, ship it.

The five parameters that matter most

  1. robot_radius (or footprint polygon) — the hard limit; obstacles closer than this are lethal.
  2. inflation_radius — the soft buffer; tune to the corridor width on your robot.
  3. controller.max_vel_x / max_vel_theta — the safe speed envelope.
  4. controller.transform_tolerance — how out-of-date a TF lookup can be before declaring failure. Increase if you see TF errors during operation.
  5. local_costmap.publish_frequency / update_frequency — too low = laggy reaction; too high = CPU starved.

Get these five right and Nav2 mostly works. The rest is tuning for specific robot quirks.

Localization choices

Nav2 needs to know where the robot is in the map. Options:

  • AMCL: particle-filter localization against a static map. The classical default. Deterministic, robust.
  • SLAM Toolbox (online): keep mapping while you navigate. Useful for unfamiliar environments.
  • Cartographer: 2D/3D SLAM, more sophisticated than SLAM Toolbox in some scenarios.
  • External: VIO, wheel + IMU, Apriltag/marker. Pipe a /pose topic into Nav2.

Common Nav2 failure modes

  • "Failed to compute control effort": controller can't find a valid velocity command. Usually inflation too high or robot too close to an obstacle. Lower inflation or improve perception.
  • Path through impossible terrain: the static map has thin gaps the robot can't fit. Re-map or use a robot-radius-aware planner like Smac.
  • Oscillation between recoveries: BT triggers spin → fail → backup → fail → repeat. Diagnose with rqt_console; usually a costmap layer is mis-published.
  • "map → odom transform not available": localization didn't converge or AMCL crashed. Check /amcl_pose for a stale or absent message.
  • Slow planning: the global costmap is too large or sub-cellular resolution is too fine. Drop resolution from 5 cm to 10 cm; planning gets 4× faster with little quality loss.

Debugging workflow

  1. Open RViz with the Nav2 panel. See the global path, local path, and costmap layers in real time.
  2. ros2 topic echo /tf and watch map → odom → base_link. If broken, fix that first.
  3. ros2 run rqt_graph rqt_graph. Verify costmap sub-layers receive sensor data.
  4. Tail journalctl -u nav2.service or the launch console. Most failures log a clear cause.

What Nav2 doesn't do

  • Multi-floor navigation — needs a separate elevator/stair-handling layer.
  • Outdoor / GPS-denied + GPS — possible but not turnkey; you wire in your own localization.
  • Multi-robot coordination — singletons; for fleets use a higher-level orchestrator.
  • Manipulation — that's MoveIt's job. Nav2 only handles the base.

The 2026 state of Nav2

Nav2 in 2026 is mature, production-grade, and used by most indoor mobile-robot vendors (TurtleBot, Misty, MIR, Fetch, MagicLab). The core architecture has been stable for years; recent additions (MPPI, dynamic obstacles, multi-robot extensions) layer on top without breaking the rest.

For new mobile robots, start with Nav2 and modify only when you hit something it can't do.

Exercise

Run TurtleBot in a simulator (ROS 2 Jazzy ships an example). Bring up Nav2 with the default params. Click a goal in RViz; watch it plan and drive. Now add a virtual wall (in Gazebo or RViz). Watch the local costmap update and the controller swerve. That five-minute demo is what 90% of indoor mobile robotics in 2026 is built on top of.

Next

MoveIt 2 — the planning + execution stack for arms. Same plug-in architecture, different problem domain.

Comments

    Sign in to post a comment.