Robot Navigation with Navigation2 (Physical Robot)
Preparation Work
- physical object
- Already ran through robot navigation Navigation2 (Simulation) once.
- This chapter only covers the general implementation approach. Generally, once you understand the simulation section, reading through the main ideas of the physical section will be enough to grasp how to implement it.
- This chapter is not from Zhao Xuzuo's tutorial; it is my own implementation approach, provided for reference only. Teacher Zhao may have a better method, but he hasn't released the hardware section tutorial yet.
- This chapter uses a 4-wheel Mecanum wheel implementation, for reference only.
All of the code below is in this GitHub repository:
https://github.com/CyberNaviRobot/CyberRobot\_ROS2\_WS
The tutorials below only include implementation ideas and won't include source code, so it's recommended to clone this repository and check the source code.
Navigation Parameter Reference
https://docs.nav2.org/configuration/index.html
SLAM localization and mapping
slam_toolbox
Based on the node description above, we need to subscribe to /scan and /tf.
Generally, LiDAR manuals provide source code for publishing /scan, so please refer to your hardware's manual for this.
/tf requires us to publish the transformation from odom_frame to base_frame. We must use C++ code to dynamically publish the coordinate transformation for odom.
However, here you need to publish /odom in order to know the robot's position and orientation, so that the robot's location in the map can be inferred.

Ensure that all parameters of the SLAM Toolbox are set correctly, especially the coordinate systems and similar settings. Other parameters can be fine-tuned according to the documentation.

Ensure the topic published by the LiDAR is /scan.

First, start the LiDAR.

Restart the URDF model and publish TF at the same time.

Finally, start SLAM.
ros2 launch mycar_slam_slam_toolbox online_sync_launch.py use_sim_time:=false



cartographer
According to the Cartographer documentation, we only need /scan and /odom.
First, open the serial port receiving node to receive data transmitted from the STM32.

Then open the odometry node and publish the odom topic.


Publish TF again. You can directly use launch to start robot_state_publisher and joint_state_publisher, and open the URDF model to publish TF.

Start the LiDAR node and publish the scan topic.

After /odom, /scan, and TF are all published, open Cartographer for mapping, then check whether the TF for map has been published.

Check the TF tree as follows:

The mapping is as follows


Map service
Save the map (serialization)
mkdir ./map
ros2 run nav2_map_server map_saver_cli -f map/my_map


Read the map (deserialization)

ros2 launch mycar_map_server map_server.launch.py
AMCL Adaptive Monte Carlo Localization
First, map data is needed to publish the /map topic.
Second, LiDAR data is needed, specifically the /scan topic.
Then a coordinate transform message is needed, specifically the /tf topic.
Then the /initial_pose topic is the initial position on the 2D map. It can be published using rviz2 or via C++ code.

Then you need to modify the parameters:
This is the official website for the parameter:
https://docs.nav2.org/configuration/packages/configuring-amcl.html#

After modifying the Launch, then modify the params parameters.

Here, OmniChassis refers not only to omnidirectional wheel chassis, but to the broader category of omnidirectional wheel chassis — for example, both omnidirectional wheel chassis and Mecanum wheel chassis fall under this category. Of course, you can also define custom chassis types.
Set use_sim_time to False at the top of this configuration file.





Navigation Server
There are too many topics involved, so we've listed them in a table:
- Subscribed topics
| Topic | interface | description |
|---|---|---|
| /goal_pose | geometry_msgs/msg/PoseStamped | Navigation target point, used to trigger a navigation task |
| /tf | tf2_msgs/msg/TFMessage | Coordinate transformation message, used for conversion between different coordinate systems. |
| /odom | nav_msgs/msg/Odometry | Odometry data provides the robot's position and motion information. |
| Topic | interface | description |
| /global_costmap/footprint | geometry_msgs/msg/Polygon | The footprint information of a robot (or any mobile platform). The footprint is the shape of the space the robot occupies on the map, typically represented as a polygon. |
| /map | nav_msgs/msg/OccupancyGrid | Publish the environment map, especially the Occupancy Grid Map used for navigation. |
| /scan | sensor_msgs/msg/LaserScan | Laser scanning data. |
| Topic | interface | description |
| /odom | nav_msgs/msg/Odometry | The robot's odometry information, including position, velocity, and orientation. |
| /speed_limit | nav2_msgs/msg/SpeedLimit | Speed limit information during navigation, used to dynamically adjust the robot's movement speed. |
| Topic | interface | description |
| /local_costmap/footprint | geometry_msgs/msg/Polygon | Footprint polygon of a robot or mobile platform, used for local costmap calculation. |
| /scan | sensor_msgs/msg/LaserScan | Laser scanner scan data, used for environmental perception and obstacle avoidance. |
| Topic | interface | description |
| /clock | rosgraph_msgs/msg/Clock | ROS system time |
| /cmd_vel_teleop | geometry_msgs/msg/Twist | Teleoperation commands for controlling the robot's linear and angular velocity. |
| /local_costmap/costmap_raw | nav2_msgs/msg/Costmap | Raw data of the local costmap |
| /local_costmap/published_footprint | geometry_msgs/msg/PolygonStamped | The robot's published footprint in the local costmap |
| /preempt_teleop | std_msgs/msg/Empty | Teleoperation preemption signal, used to interrupt the current teleoperation. |
| Topic | interface | description |
| /global_costmap/costmap_raw | nav2_msgs/msg/Costmap | Raw data of the global costmap, used for path planning. |
| /global_costmap/published_footprint | geometry_msgs/msg/PolygonStamped | The robot's footprint representation in the global costmap |
| Topic | interface | description |
| /cmd_vel_nav | geometry_msgs/msg/Twist | Topic for receiving velocity control commands from other nodes |
- Published topics
| Topic | interface | description |
|---|---|---|
| /plan | nav_msgs/msg/Path | Global path from current position to target point |
| Topic | interface | description |
| /global_costmap/costmap | nav_msgs/msg/OccupancyGrid | Publish the current state of the global costmap. |
| /global_costmap/costmap_raw | nav2_msgs/msg/Costmap | Raw costmap data without further processing. |
| /global_costmap/costmap_updates | map_msgs/msg/OccupancyGridUpdate | The update of the global costmap, this message can efficiently update the map. |
| /global_costmap/published_footprint | geometry_msgs/msg/PolygonStamped | Publish the robot's footprint, which is the shape of the space the robot occupies on the map. |
| Topic Name | Message type | description |
| /cmd_vel_nav | geometry_msgs/msg/Twist | Publish control commands, including linear and angular velocity, to move the robot along the planned path. |
| /cost_cloud | sensor_msgs/msg/PointCloud2 | Publish point cloud data in the cost map for obstacle avoidance and path planning. |
| /local_plan | nav_msgs/msg/Path | Publish the local path planning result, i.e., how the robot should reach a point near the current target point. |
| /marker | visualization_msgs/msg/MarkerArray | Publish visualization markers to display information such as paths and obstacles in visualization tools like RViz. |
| /received_global_plan | nav_msgs/msg/Path | Publishes the global path received from the global planner, i.e., the path from the current position to the target point. |
| /transformed_global_plan | nav_msgs/msg/Path | Publish the globally transformed path after coordinate transformation, ensuring the path is consistent with the robot's current coordinate system. |
| Topic | interface | description |
| /local_costmap/clearing_endpoints | sensor_msgs/msg/PointCloud2 | Remove obstacle point cloud data from the cost map, typically used for dynamic obstacle handling. |
| /local_costmap/costmap | nav_msgs/msg/OccupancyGrid | Local cost map, representing the traversability of the environment around the robot. |
| /local_costmap/costmap_raw | nav2_msgs/msg/Costmap | Unprocessed local cost map, which may contain more detailed information. |
| /local_costmap/costmap_updates | map_msgs/msg/OccupancyGridUpdate | Local costmap update information, including which areas have changed. |
| /local_costmap/published_footprint | geometry_msgs/msg/PolygonStamped | The published robot footprint polygon, with timestamps indicating the publication time. |
| /local_costmap/voxel_grid | nav2_msgs/msg/VoxelGrid | Voxel grid data, used for spatial partitioning and optimization in cost map generation. |
| Topic | interface | description |
| /cmd_vel | geometry_msgs/msg/Twist | Speed command sent to the low-level controller. |
| Topic | interface | description |
| /plan_smoothed | nav_msgs/msg/Path | The smoothed global path |
| Topic | interface | description |
| /cmd_vel | geometry_msgs/msg/Twist | Publish the topic for speed control commands after processing or smoothing. |
Since Zhao Xuzuo rewrote the official source code into the workspace, this can be cumbersome for beginners. For beginners, it is recommended to use the official bringup node. Below is a summary of the usage method based on the official Wiki: (Here, the official bringup node is chosen instead of Teacher Zhao Xuzuo's node.)
Below is the configured nav2.launch.py file:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration
# from launch_ros.actions import Node
def generate_launch_description():
navigation2_dir = get_package_share_directory('nav05_navigation2')
nav2_bringup_dir = get_package_share_directory('nav2_bringup')
# launch的参数的优先级比yaml的参数优先级高
use_sim_time = LaunchConfiguration('use_sim_time', default='flase')
map_yaml_path = LaunchConfiguration('map',default=os.path.join(navigation2_dir,'map','house.yaml'))
nav2_param_path = LaunchConfiguration('params_file',default=os.path.join(navigation2_dir,'params','nav2.yaml'))
return LaunchDescription([
DeclareLaunchArgument('use_sim_time',default_value=use_sim_time,description='Use simulation (Gazebo) clock if true'),
DeclareLaunchArgument('map',default_value=map_yaml_path,description='Full path to map file to load'),
DeclareLaunchArgument('params_file',default_value=nav2_param_path,description='Full path to param file to load'),
IncludeLaunchDescription(
PythonLaunchDescriptionSource([nav2_bringup_dir,'/launch','/bringup_launch.py']),
launch_arguments={
'map': map_yaml_path,
'use_sim_time': use_sim_time,
'params_file': nav2_param_path}.items(),
),
])
Here is rviz2.launch.py:
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
navigation2_dir = get_package_share_directory('nav05_navigation2')
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
rviz_config_dir = os.path.join(navigation2_dir,'rviz','nav2.rviz')
return LaunchDescription([
DeclareLaunchArgument('use_sim_time',default_value=use_sim_time,description='Use simulation (Gazebo) clock if true'),
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', rviz_config_dir],
parameters=[{'use_sim_time': use_sim_time}],
output='screen'),
])
Below is the nav2.yaml configuration file (differential drive model + DWE local planner):
amcl:
ros__parameters:
use_sim_time: false
alpha1: 0.2
alpha2: 0.2
alpha3: 0.2
alpha4: 0.2
alpha5: 0.2
base_frame_id: "base_link"
beam_skip_distance: 0.5
beam_skip_error_threshold: 0.9
beam_skip_threshold: 0.3
do_beamskip: false
global_frame_id: "map"
lambda_short: 0.1
laser_likelihood_max_dist: 2.0
laser_max_range: 100.0
laser_min_range: -1.0
laser_model_type: "likelihood_field"
max_beams: 60
max_particles: 2000
min_particles: 500
odom_frame_id: "odom"
pf_err: 0.05
pf_z: 0.99
recovery_alpha_fast: 0.0
recovery_alpha_slow: 0.0
resample_interval: 1
robot_model_type: "nav2_amcl::DifferentialMotionModel"
save_pose_rate: 0.5
sigma_hit: 0.2
tf_broadcast: true
transform_tolerance: 1.0
update_min_a: 0.2
update_min_d: 0.25
z_hit: 0.5
z_max: 0.05
z_rand: 0.5
z_short: 0.05
scan_topic: scan
amcl_map_client:
ros__parameters:
use_sim_time: false
amcl_rclcpp_node:
ros__parameters:
use_sim_time: false
bt_navigator:
ros__parameters:
use_sim_time: false
global_frame: map
robot_base_frame: base_link
odom_topic: /odom
bt_loop_duration: 10
default_server_timeout: 20
# 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults:
# nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml
# nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml
# They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2.
plugin_lib_names:
- nav2_compute_path_to_pose_action_bt_node
- nav2_compute_path_through_poses_action_bt_node
- nav2_smooth_path_action_bt_node
- nav2_follow_path_action_bt_node
- nav2_spin_action_bt_node
- nav2_wait_action_bt_node
- nav2_back_up_action_bt_node
- nav2_drive_on_heading_bt_node
- nav2_clear_costmap_service_bt_node
- nav2_is_stuck_condition_bt_node
- nav2_goal_reached_condition_bt_node
- nav2_goal_updated_condition_bt_node
- nav2_globally_updated_goal_condition_bt_node
- nav2_is_path_valid_condition_bt_node
- nav2_initial_pose_received_condition_bt_node
- nav2_reinitialize_global_localization_service_bt_node
- nav2_rate_controller_bt_node
- nav2_distance_controller_bt_node
- nav2_speed_controller_bt_node
- nav2_truncate_path_action_bt_node
- nav2_truncate_path_local_action_bt_node
- nav2_goal_updater_node_bt_node
- nav2_recovery_node_bt_node
- nav2_pipeline_sequence_bt_node
- nav2_round_robin_node_bt_node
- nav2_transform_available_condition_bt_node
- nav2_time_expired_condition_bt_node
- nav2_path_expiring_timer_condition
- nav2_distance_traveled_condition_bt_node
- nav2_single_trigger_bt_node
- nav2_is_battery_low_condition_bt_node
- nav2_navigate_through_poses_action_bt_node
- nav2_navigate_to_pose_action_bt_node
- nav2_remove_passed_goals_action_bt_node
- nav2_planner_selector_bt_node
- nav2_controller_selector_bt_node
- nav2_goal_checker_selector_bt_node
- nav2_controller_cancel_bt_node
- nav2_path_longer_on_approach_bt_node
- nav2_wait_cancel_bt_node
- nav2_spin_cancel_bt_node
- nav2_back_up_cancel_bt_node
- nav2_drive_on_heading_cancel_bt_node
bt_navigator_rclcpp_node:
ros__parameters:
use_sim_time: false
controller_server:
ros__parameters:
use_sim_time: false
controller_frequency: 20.0
min_x_velocity_threshold: 0.001
min_y_velocity_threshold: 0.5
min_theta_velocity_threshold: 0.001
failure_tolerance: 0.3
progress_checker_plugin: "progress_checker"
goal_checker_plugins: ["general_goal_checker"] # "precise_goal_checker"
controller_plugins: ["FollowPath"]
# Progress checker parameters
progress_checker:
plugin: "nav2_controller::SimpleProgressChecker"
required_movement_radius: 0.5
movement_time_allowance: 10.0
# Goal checker parameters
#precise_goal_checker:
# plugin: "nav2_controller::SimpleGoalChecker"
# xy_goal_tolerance: 0.25
# yaw_goal_tolerance: 0.25
# stateful: True
general_goal_checker:
stateful: True
plugin: "nav2_controller::SimpleGoalChecker"
xy_goal_tolerance: 0.25
yaw_goal_tolerance: 0.25
# DWB parameters
FollowPath:
plugin: "dwb_core::DWBLocalPlanner"
debug_trajectory_details: True
min_vel_x: 0.0
min_vel_y: 0.0
max_vel_x: 0.26
max_vel_y: 0.0
max_vel_theta: 1.0
min_speed_xy: 0.0
max_speed_xy: 0.26
min_speed_theta: 0.0
# Add high threshold velocity for turtlebot 3 issue.
# https://github.com/ROBOTIS-GIT/turtlebot3_simulations/issues/75
acc_lim_x: 2.5
acc_lim_y: 0.0
acc_lim_theta: 3.2
decel_lim_x: -2.5
decel_lim_y: 0.0
decel_lim_theta: -3.2
vx_samples: 20
vy_samples: 5
vtheta_samples: 20
sim_time: 1.7
linear_granularity: 0.05
angular_granularity: 0.025
transform_tolerance: 0.2
xy_goal_tolerance: 0.25
trans_stopped_velocity: 0.25
short_circuit_trajectory_evaluation: True
stateful: True
critics: ["RotateToGoal", "Oscillation", "BaseObstacle", "GoalAlign", "PathAlign", "PathDist", "GoalDist"]
BaseObstacle.scale: 0.02
PathAlign.scale: 32.0
PathAlign.forward_point_distance: 0.1
GoalAlign.scale: 24.0
GoalAlign.forward_point_distance: 0.1
PathDist.scale: 32.0
GoalDist.scale: 24.0
RotateToGoal.scale: 32.0
RotateToGoal.slowing_factor: 5.0
RotateToGoal.lookahead_time: -1.0
controller_server_rclcpp_node:
ros__parameters:
use_sim_time: false
local_costmap:
local_costmap:
ros__parameters:
update_frequency: 5.0
publish_frequency: 2.0
global_frame: odom
robot_base_frame: base_link
use_sim_time: false
rolling_window: true
width: 3
height: 3
resolution: 0.05
robot_radius: 0.22
plugins: ["static_layer", "obstacle_layer", "voxel_layer", "inflation_layer"]
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.55
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
enabled: True
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
voxel_layer:
plugin: "nav2_costmap_2d::VoxelLayer"
enabled: True
publish_voxel_map: True
origin_z: 0.0
z_resolution: 0.05
z_voxels: 16
max_obstacle_height: 2.0
mark_threshold: 0
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
map_subscribe_transient_local: True
always_send_full_costmap: True
local_costmap_client:
ros__parameters:
use_sim_time: false
local_costmap_rclcpp_node:
ros__parameters:
use_sim_time: false
global_costmap:
global_costmap:
ros__parameters:
update_frequency: 1.0
publish_frequency: 1.0
global_frame: map
robot_base_frame: base_link
use_sim_time: false
robot_radius: 0.22
resolution: 0.05
track_unknown_space: true
plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
obstacle_layer:
plugin: "nav2_costmap_2d::ObstacleLayer"
enabled: True
observation_sources: scan
scan:
topic: /scan
max_obstacle_height: 2.0
clearing: True
marking: True
data_type: "LaserScan"
raytrace_max_range: 3.0
raytrace_min_range: 0.0
obstacle_max_range: 2.5
obstacle_min_range: 0.0
static_layer:
plugin: "nav2_costmap_2d::StaticLayer"
map_subscribe_transient_local: True
inflation_layer:
plugin: "nav2_costmap_2d::InflationLayer"
cost_scaling_factor: 3.0
inflation_radius: 0.55
always_send_full_costmap: True
global_costmap_client:
ros__parameters:
use_sim_time: false
global_costmap_rclcpp_node:
ros__parameters:
use_sim_time: false
map_server:
ros__parameters:
use_sim_time: false
yaml_filename: "house.yaml"
map_saver:
ros__parameters:
use_sim_time: false
save_map_timeout: 5.0
free_thresh_default: 0.25
occupied_thresh_default: 0.65
map_subscribe_transient_local: True
planner_server:
ros__parameters:
expected_planner_frequency: 20.0
use_sim_time: false
planner_plugins: ["GridBased"]
GridBased:
plugin: "nav2_navfn_planner/NavfnPlanner"
tolerance: 0.5
use_astar: false
allow_unknown: true
planner_server_rclcpp_node:
ros__parameters:
use_sim_time: false
smoother_server:
ros__parameters:
use_sim_time: false
smoother_plugins: ["simple_smoother"]
simple_smoother:
plugin: "nav2_smoother::SimpleSmoother"
tolerance: 1.0e-10
max_its: 1000
do_refinement: True
behavior_server:
ros__parameters:
costmap_topic: local_costmap/costmap_raw
footprint_topic: local_costmap/published_footprint
cycle_frequency: 10.0
behavior_plugins: ["spin", "backup", "drive_on_heading", "wait"]
spin:
plugin: "nav2_behaviors/Spin"
backup:
plugin: "nav2_behaviors/BackUp"
drive_on_heading:
plugin: "nav2_behaviors/DriveOnHeading"
wait:
plugin: "nav2_behaviors/Wait"
global_frame: odom
robot_base_frame: base_link
transform_tolerance: 0.1
use_sim_time: false
simulate_ahead_time: 2.0
max_rotational_vel: 1.0
min_rotational_vel: 0.4
rotational_acc_lim: 3.2
robot_state_publisher:
ros__parameters:
use_sim_time: false
waypoint_follower:
ros__parameters:
loop_rate: 20
stop_on_failure: false
waypoint_task_executor_plugin: "wait_at_waypoint"
wait_at_waypoint:
plugin: "nav2_waypoint_follower::WaitAtWaypoint"
enabled: True
waypoint_pause_duration: 200
velocity_smoother:
ros__parameters:
use_sim_time: false
smoothing_frequency: 20.0
scale_velocities: False
feedback: "OPEN_LOOP"
max_velocity: [0.26, 0.0, 1.0]
min_velocity: [-0.26, 0.0, -1.0]
max_accel: [2.5, 0.0, 3.2]
max_decel: [-2.5, 0.0, -3.2]
odom_topic: "odom"
odom_duration: 0.1
deadband_velocity: [0.0, 0.0, 0.0]
velocity_timeout: 1.0