Path Planning November 1, 2024

ROS 2 Nav2: What It Does Well and Where It Falls Short

ROS 2 system diagram abstract concept for path planning

Nav2 is the de facto navigation stack for ROS 2 robots in logistics. If you are building or deploying an AMR today, there is a high probability Nav2 is running somewhere in your stack — either directly, or with a vendor-modified fork on top. It deserves an honest assessment: where it is genuinely good, where its design decisions create friction in real deployments, and where the gaps are large enough that you need to supplement or replace components.

We built Mobvynt to plug into Nav2's architecture rather than replace it. That choice means we have spent a lot of time understanding what Nav2 does, what assumptions it bakes in, and where those assumptions break. This post is that assessment.

What Nav2 Does Well

Global Path Planning

NavFn (Nav2's default global planner using Dijkstra's or A* over the static costmap) is solid. For a known environment with a reasonably accurate static map, it finds globally optimal or near-optimal paths reliably and fast. On a 200×200 m occupancy grid at 5 cm resolution, NavFn typically completes a global plan in under 100 ms on commodity hardware. The Smac Planner (the hybrid-A* planner added in later Nav2 releases) adds kinematic feasibility and is worthwhile for differential-drive robots that need to account for turning radius.

The global planner's robustness is one of Nav2's strongest points. It handles complex facility geometries — multi-aisle layouts, cross-passages, staging areas with partial blockages — without pathological behavior. The plan it produces is a reasonable starting point for execution even if the local planner will deviate from it significantly.

Layered Costmap Architecture

The costmap2D layered architecture is well-designed. Static map layer, obstacle layer, inflation layer — each has a clear responsibility and the plugin architecture makes it relatively straightforward to add custom layers. We publish Mobvynt's typed obstacle detections as a custom costmap layer that sits between the obstacle layer and inflation layer, which is exactly the extension point the architecture intended for this use case.

The inflation layer's ability to create cost gradients around obstacles — rather than hard binary occupied/free boundaries — is important for smooth navigation. Robots navigating on binary costmaps hug obstacles too closely; cost gradients create natural clearance margins. Nav2's default inflation_radius and cost_scaling_factor parameters tune this reasonably, though the defaults usually need adjustment for specific facilities.

Behavior Trees and Recovery

Nav2's behavior tree-based task execution model (via nav2_bt_navigator) is a significant improvement over ROS 1's move_base recovery behavior chain. Being able to define and customize the recovery sequence as a behavior tree means operators can tune the robot's response to plan failures without modifying source code. "Try to clear costmap, re-plan globally, if still stuck spin in place to update local observations, re-plan again" — that sequence is configurable and testable.

The default behavior trees bundled with Nav2 cover the common cases well. For most warehouse deployments, the navigate_to_pose with recovery BT is a reasonable starting point.

Where Nav2 Falls Short in High-Traffic Environments

Dynamic Obstacle Handling

This is the most significant gap for warehouse deployments. Nav2's obstacle layer treats every sensor detection as equal — a person walking at 1.0 m/s and a pallet that has been stationary for three weeks both inflate the costmap with the same cost profile. There is no concept of obstacle class, no trajectory prediction, no confidence decay based on observation history.

The practical consequences:

  • The robot cannot distinguish between a temporary obstacle that will clear in 2 seconds (a person walking across the aisle) and a permanent obstacle it should route around. Both get the same inflation treatment.
  • Fast-moving obstacles — a forklift reversing at 1.5 m/s — may exit the robot's sensor range between planning cycles, causing the costmap to show a briefly-inflated cost region that the planner routes around, only for the forklift to have moved further into the robot's planned path by the time it executes that plan. The temporal mismatch between when the observation was made and when the plan executes is Nav2's fundamental problem with fast-moving obstacles.
  • The local planner (DWA or TEB) reacts to the current costmap state but has no memory of previous states. A dense obstacle environment that briefly clears will cause the robot to attempt a path that was previously blocked, then encounter the obstacle again when it returns, leading to oscillation.

The Costmap Update Frequency Tension

Nav2's local costmap updates at a configurable frequency, typically 5 to 10 Hz in production deployments. At 10 Hz, you are getting a new costmap every 100 ms. The local planner runs against this costmap and generates velocity commands. The problem is that a forklift traveling at 1.5 m/s moves 150 mm between costmap updates. At a planning horizon of 1.5 meters, you have 10 planning cycles before the forklift reaches the robot — but each of those cycles is operating on a costmap that is at least 100 ms stale for the forklift's position.

Increasing the costmap update frequency helps but creates a compute budget problem. On embedded robot hardware — typical compute is an Intel NUC with a Core i5 or i7 — running the full costmap pipeline at 20 Hz while simultaneously running LiDAR processing, AMCL localization, the global planner, and the behavior tree manager creates contention that manifests as planning latency spikes. You cannot simply double the costmap frequency without consequences for overall navigation stability.

No Native WMS Integration

Nav2 knows nothing about timing constraints. It plans the fastest feasible path to the goal without any concept of "this pick slot has a 90-second arrival window from now." If the globally optimal path is blocked and requires replanning, Nav2 will find the next-best path — which may or may not arrive within the WMS-specified window. The navigation stack has no awareness that the window is closing and no ability to trade off path risk against timing deadline.

This means the WMS timing constraint logic has to live outside Nav2, typically in the mission management layer that dispatches goals. That works as a rough gate (cancel the mission if the estimated arrival time exceeds the deadline) but does not enable the more useful behavior: choosing a slightly riskier path to preserve a timing window when the conservative path would miss it. That kind of constraint-aware planning requires the planner to know about the constraint at planning time — which the Nav2 interface does not naturally support.

How We Use Nav2 as a Foundation

Mobvynt does not replace Nav2. It adds the components that Nav2's architecture does not include. Specifically:

MV Perceive publishes typed, tracked obstacle detections to a custom costmap layer that extends Nav2's local costmap. Each tracked obstacle has a class label, a confidence score, and a predicted position at T+500ms. The planner can query this layer for trajectory-aware cost estimates rather than the instantaneous snapshot that the standard obstacle layer provides.

MV Plan wraps Nav2's Smac Planner with a timing constraint solver. The constraint solver accepts a WMS-sourced arrival deadline, evaluates the planned path's estimated completion time against that deadline, and adjusts the planner's cost weighting between path length (efficiency) and obstacle clearance (safety) based on deadline urgency. If the deadline is 120 seconds away and the optimal path takes 90 seconds, the planner stays conservative. If the deadline is 80 seconds away and the optimal path takes 85 seconds, the planner can accept a shorter, slightly higher-risk alternative.

The result is a stack that still uses Nav2's global planner and behavior tree framework — which are genuinely good — while replacing the perception-to-costmap pipeline and adding constraint-aware planning. The integration subscribes to standard Nav2 topics:

# Standard Nav2 subscriptions (Mobvynt receives)
/scan                          # LaserScan from LiDAR
/camera/color/image_raw        # optional RGBD
/odom                          # Odometry

# Mobvynt publishes to Nav2-compatible topics
/mobvynt/obstacle_layer        # custom costmap layer update
/mobvynt/path                  # NavPath override (when constraint active)
/cmd_vel                       # velocity commands at 20 Hz

This architecture means Nav2 handles what it is good at — localization via AMCL, global path planning via Smac Planner, behavior tree recovery — and Mobvynt handles the dynamic obstacle detection and constraint-aware re-planning that Nav2 does not natively support. We are not saying Nav2 is inadequate for simple environments. We are saying that for high-traffic logistics environments where obstacle density is significant and timing constraints are real, the gaps are large enough to be operationally costly if left unaddressed.