Skip to main content
🗺️ SMAC

ROS 2 Nav2 SMAC Planner Guide 2026

SMAC (State Machine-based A*) planners generate kinematically feasible SE(2) paths — accounting for turning radius, non-holonomic constraints, and forward-only motion. Essential for car-like robots, forklifts, and any vehicle that can't spin in place.

Planner Selection: Which One?

PlannerAlgorithmRobot TypeSpeed
NavFn (Dijkstra/A*)Grid-based A*Diff-drive (any heading at goal)Fast, ~50ms
SmacPlanner2DGrid A* + footprintDiff-drive (circular footprint OK)Fast
SmacPlannerHybridHybrid-A* (Reeds-Shepp/Dubins)Car-like, Ackermann, forkliftsModerate, ~200ms
SmacPlannerLatticeLattice A* + motion primitivesAny kinematic model (legged, etc.)Slower, configurable

Rule: If your robot has a minimum turning radius > 0 (car, forklift, AGV), use SmacPlannerHybrid. If you need to precompute motion primitives for a custom kinematic model, use SmacPlannerLattice. Diff-drive robots that can spin in place: NavFn or SmacPlanner2D is simpler and faster.

Install

sudo apt install ros-jazzy-nav2-smac-planner
# or for Humble:
sudo apt install ros-humble-nav2-smac-planner

SmacPlannerHybrid: Full YAML Reference

Add to your nav2_params.yaml under the planner_server:

planner_server:
  ros__parameters:
    planner_plugins: ["GridBased"]
    GridBased:
      plugin: "nav2_smac_planner/SmacPlannerHybrid"

      # --- Core kinematic constraints ---
      minimum_turning_radius: 0.40   # meters. CRITICAL: measure your robot!
                                      # Car: wheelbase / tan(max_steer_angle)
      motion_model_for_search: "REEDS_SHEPP"  # REEDS_SHEPP | DUBIN
                                               # REEDS_SHEPP allows reverse motion
                                               # DUBIN = forward only

      # --- Angular resolution ---
      angle_quantization_bins: 72    # 360/72 = 5° per bin. More bins = finer
                                     # heading resolution but slower search.
                                     # 72 (5°) is good for most robots.
                                     # Forklifts with tight turns: try 72-144.

      # --- Search space ---
      max_iterations: 1000000        # Upper bound on A* node expansions
      max_planning_time: 5.0         # seconds before planner gives up
      tolerance: 0.5                 # meters, goal tolerance
      cache_obstacle_heuristic: True # reuse heuristic across replanning calls

      # --- Analytic expansion (Reeds-Shepp shortcut to goal) ---
      analytic_expansion_ratio: 3.5  # Try analytic shortcut every N iterations
      analytic_expansion_max_length: 3.0  # meters, max analytic segment length

      # --- Reversing ---
      allow_unknown: True            # plan through unknown cells
      reverse_penalty: 2.0           # cost multiplier for reverse motion
                                     # Higher = prefer forward-only paths
      change_penalty: 0.0            # penalty for changing direction (fwd→rev)
      non_straight_penalty: 1.20     # penalty for curved vs straight segments
      cost_penalty: 2.0              # costmap lethal threshold amplifier

      # --- Costmap ---
      use_quadratic_cost_penalty: False
      downsample_costmap: False
      downsampling_factor: 1

      # --- Smoother (optional post-processing) ---
      smooth_path: True
      smoother:
        max_iterations: 1000
        w_smooth: 0.3
        w_data: 0.2
        tolerance: 0.001
        do_refinement: True
        refinement_num: 2

Calculating minimum_turning_radius

Formula for Ackermann / Car-like robots:

minimum_turning_radius = wheelbase / tan(max_steering_angle_radians)

# Example: wheelbase = 0.35m, max_steer = 30°
import math
r = 0.35 / math.tan(math.radians(30))  # → 0.606m

# Example: differential drive with minimum in-place turn radius
# (e.g. due to mechanical stops — if 0, robot can spin in place, use NavFn)

Common mistake: Setting minimum_turning_radius: 0.0 for a car-like robot disables the constraint — the planner generates infeasible paths the controller can't follow, causing oscillation. Always measure with the actual robot's steering range.

REEDS_SHEPP vs DUBIN Motion Model

REEDS_SHEPP

  • ✅ Forward AND reverse motion
  • ✅ Shorter paths in tight spaces
  • ✅ Good for forklifts and indoor AGVs
  • ⚠️ Reverse segments may be unsafe with rear obstacles

DUBIN

  • ✅ Forward-only — always safe for passengers
  • ✅ Simpler for outdoor robots and cars
  • ⚠️ May fail in very tight spaces (no reverse)
  • ⚠️ Longer paths in cluttered environments

SmacPlannerLattice: YAML Reference

The lattice planner uses precomputed motion primitives for any kinematic model. Generate them with nav2_smac_planner's primitive generator first:

# Generate motion primitives (one-time setup)
ros2 run nav2_smac_planner lattice_primitives \
  --output /tmp/my_robot_primitives.json \
  --turning_radius 0.4 \
  --num_of_headings 16 \
  --grid_resolution 0.05

# nav2_params.yaml
planner_server:
  ros__parameters:
    planner_plugins: ["GridBased"]
    GridBased:
      plugin: "nav2_smac_planner/SmacPlannerLattice"
      allow_unknown: True
      tolerance: 0.25
      max_iterations: 4000000
      max_planning_time: 5.0
      analytic_expansion_ratio: 3.5
      analytic_expansion_max_length: 3.0
      reverse_penalty: 2.1
      change_penalty: 0.05
      non_straight_penalty: 1.05
      cost_penalty: 2.0
      rotation_penalty: 5.0
      retrospective_penalty: 0.025
      lattice_filepath: "/tmp/my_robot_primitives.json"
      cache_obstacle_heuristic: False
      allow_reverse_expansion: False
      smooth_path: True
      smoother:
        max_iterations: 1000
        w_smooth: 0.3
        w_data: 0.2
        tolerance: 0.001

Debugging & Tuning

Path ignores obstacles / cuts through walls

Increase cost_penalty (try 2.0→3.0). Check that the global costmap inflation_radius matches your robot footprint inscribed radius. SMAC uses the costmap cost values directly.

Planning fails ('No valid path found')

Increase max_iterations (1M → 2M) and max_planning_time (5→15s). Check minimum_turning_radius isn't too large for the corridor width. Try REEDS_SHEPP if DUBIN fails in tight spaces.

Path oscillates / controller can't follow

Enable smooth_path: True and increase smoother w_smooth. Also increase angle_quantization_bins (36→72) so heading transitions are finer. Check FollowPath controller max_vel_theta matches path curvature.

Visualize path headings in RViz2

ros2 topic echo /plan | grep pose | head -20 — check orientation quaternions are smooth. In RViz2: Add → Path (topic /plan). Check arrow markers if using rviz_visual_tools.

Next Steps