Skip to main content

I4: Nav2 Basics

Autonomous Navigation with the ROS 2 Navigation Stack


Learning Objectives​

By the end of this lesson, you will be able to:

  • Launch Nav2 with a pre-built map
  • Send navigation goals programmatically
  • Monitor navigation status and handle results
  • Configure basic Nav2 parameters

Prerequisites​


Theory: Nav2 Architecture​

Core Components​

ComponentPackagePurpose
Map Servernav2_map_serverLoads and publishes the map
AMCLnav2_amclLocalizes robot on map
Planner Servernav2_plannerComputes global paths
Controller Servernav2_controllerFollows paths (local planning)
Behavior Servernav2_behaviorsRecovery behaviors
BT Navigatornav2_bt_navigatorCoordinates via behavior tree
Goal Received → Global Planner → Path → Controller → cmd_vel → Robot Moves
↑ ↓
Costmap Obstacle Detected?
↓
Recovery Behaviors

Key Topics​

TopicMessage TypeDirection
/goal_posegeometry_msgs/PoseStampedInput
/plannav_msgs/PathOutput
/cmd_velgeometry_msgs/TwistOutput
/mapnav_msgs/OccupancyGridInput
/scansensor_msgs/LaserScanInput

Code Example: Navigation Goal Sender​

This node sends navigation goals and monitors progress:

#!/usr/bin/env python3
"""
nav2_goal_sender.py

Sends navigation goals to Nav2 and monitors progress.
Demonstrates Nav2 action client usage.

ROS 2 Actions:
Client: /navigate_to_pose (nav2_msgs/NavigateToPose)

Dependencies: rclpy, nav2_msgs, geometry_msgs, rclpy.action

Usage:
ros2 run <package_name> nav2_goal_sender
"""

import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from nav2_msgs.action import NavigateToPose
from geometry_msgs.msg import PoseStamped
from rclpy.duration import Duration


class Nav2GoalSender(Node):
"""ROS 2 node that sends navigation goals to Nav2."""

def __init__(self):
super().__init__('nav2_goal_sender')

# Nav2 action client
self._action_client = ActionClient(
self,
NavigateToPose,
'navigate_to_pose'
)

self.get_logger().info('Waiting for Nav2 action server...')
self._action_client.wait_for_server()
self.get_logger().info('Nav2 action server available!')

def send_goal(self, x: float, y: float, yaw: float = 0.0):
"""Send a navigation goal to Nav2.

Args:
x: Target X position in map frame (meters)
y: Target Y position in map frame (meters)
yaw: Target orientation (radians)
"""
goal_msg = NavigateToPose.Goal()

# Create target pose
goal_msg.pose.header.frame_id = 'map'
goal_msg.pose.header.stamp = self.get_clock().now().to_msg()

# Position
goal_msg.pose.pose.position.x = x
goal_msg.pose.pose.position.y = y
goal_msg.pose.pose.position.z = 0.0

# Orientation (quaternion from yaw)
import math
goal_msg.pose.pose.orientation.x = 0.0
goal_msg.pose.pose.orientation.y = 0.0
goal_msg.pose.pose.orientation.z = math.sin(yaw / 2)
goal_msg.pose.pose.orientation.w = math.cos(yaw / 2)

self.get_logger().info(f'Sending goal: ({x:.2f}, {y:.2f}, yaw={yaw:.2f})')

# Send goal with callbacks
send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback
)
send_goal_future.add_done_callback(self.goal_response_callback)

def goal_response_callback(self, future):
"""Handle goal acceptance/rejection."""
goal_handle = future.result()

if not goal_handle.accepted:
self.get_logger().error('Goal was rejected!')
return

self.get_logger().info('Goal accepted!')

# Get result
result_future = goal_handle.get_result_async()
result_future.add_done_callback(self.result_callback)

def feedback_callback(self, feedback_msg):
"""Handle navigation feedback."""
feedback = feedback_msg.feedback
current_pose = feedback.current_pose.pose.position

# Log progress periodically
self.get_logger().info(
f'Current position: ({current_pose.x:.2f}, {current_pose.y:.2f})'
)

def result_callback(self, future):
"""Handle navigation result."""
result = future.result().result

self.get_logger().info('Navigation completed!')

# Optionally send another goal or shutdown
# self.send_goal(0.0, 0.0) # Return home


def main(args=None):
rclpy.init(args=args)
node = Nav2GoalSender()

# Send example goal
node.send_goal(x=2.0, y=1.0, yaw=0.0)

try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()


if __name__ == '__main__':
main()

Code Example: Navigation Launch File​

#!/usr/bin/env python3
"""
navigation_launch.py

Launch file for Nav2 navigation stack.
Loads map and starts all navigation components.

Usage:
ros2 launch <package_name> navigation_launch.py map:=/path/to/map.yaml
"""

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os


def generate_launch_description():
# Get package directories
nav2_bringup_dir = get_package_share_directory('nav2_bringup')

# Launch arguments
map_file = LaunchConfiguration('map')
use_sim_time = LaunchConfiguration('use_sim_time', default='true')
params_file = LaunchConfiguration('params_file')

# Default Nav2 params file
default_params = os.path.join(
nav2_bringup_dir, 'params', 'nav2_params.yaml'
)

return LaunchDescription([
# Arguments
DeclareLaunchArgument(
'map',
description='Full path to map yaml file'
),
DeclareLaunchArgument(
'use_sim_time',
default_value='true',
description='Use simulation clock'
),
DeclareLaunchArgument(
'params_file',
default_value=default_params,
description='Nav2 parameters file'
),

# Include Nav2 bringup
IncludeLaunchDescription(
PythonLaunchDescriptionSource(
os.path.join(nav2_bringup_dir, 'launch', 'bringup_launch.py')
),
launch_arguments={
'map': map_file,
'use_sim_time': use_sim_time,
'params_file': params_file,
}.items()
),
])

Demonstration: Running Navigation​

Step 1: Launch Simulation​

# Terminal 1: Launch Gazebo
export TURTLEBOT3_MODEL=waffle
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py

Step 2: Launch Navigation​

# Terminal 2: Launch Nav2 with map
ros2 launch turtlebot3_navigation2 navigation2.launch.py \
map:=/path/to/your/map.yaml \
use_sim_time:=true

Step 3: Set Initial Pose in RViz2​

  1. Click 2D Pose Estimate
  2. Click on map where robot actually is
  3. Drag to set orientation

Step 4: Send Goals​

Option A: RViz2

  1. Click Nav2 Goal
  2. Click destination on map

Option B: Code

# Terminal 3: Run goal sender
python3 nav2_goal_sender.py

Option C: Command Line

ros2 action send_goal /navigate_to_pose nav2_msgs/action/NavigateToPose \
"{pose: {header: {frame_id: 'map'}, pose: {position: {x: 2.0, y: 1.0}}}}"

Monitoring Navigation​

Key Topics to Watch​

# See the computed path
ros2 topic echo /plan

# See velocity commands
ros2 topic echo /cmd_vel

# See robot position
ros2 topic echo /amcl_pose
# Check if Nav2 nodes are active
ros2 lifecycle list /controller_server
ros2 lifecycle list /planner_server

Key Concepts Summary​

ConceptDescription
NavigateToPoseAction to send navigation goals
AMCLParticle filter localization
Planner ServerComputes global path
Controller ServerFollows path locally
Behavior TreeCoordinates navigation logic

Hands-On Exercise​

Exercise I4.1: Navigate to Multiple Waypoints​

Modify nav2_goal_sender.py to:

  1. Store a list of waypoints
  2. Navigate to each waypoint in sequence
  3. Wait for each goal to complete before sending the next

Exercise I4.2: Handle Navigation Failures​

Add error handling for:

  1. Goal rejection (obstacle at goal location)
  2. Navigation timeout (robot stuck)
  3. Path planning failure (no path exists)

AI Agent Assisted Prompts​

Prompt 1: Navigation Tuning​

My robot reaches its navigation goals but stops about 30cm short. What Nav2
parameters control goal tolerance? How do I make the robot get closer to the
exact goal position?

Prompt 2: Dynamic Obstacles​

My robot navigates around static obstacles but collides with moving people.
How does Nav2 handle dynamic obstacles? What parameters affect reaction time?

Prompt 3: Multi-Robot Navigation​

I'm deploying multiple robots in the same environment. How do I configure
Nav2 to avoid robot-robot collisions? What about coordinating paths?

Summary​

In this lesson, you learned:

  1. Nav2 components: Map Server, AMCL, Planner, Controller, Behaviors
  2. How to launch Nav2 with a pre-built map
  3. How to send navigation goals programmatically
  4. How to monitor navigation status and results
  5. Basic Nav2 action client usage

Next Steps​

Congratulations! You've completed the Intermediate tier!

You can now build perception pipelines, manage transforms, run SLAM, and navigate autonomously!