Using nav2#
Added in version Jazzy: This section.
Warning
This topic is under construction and this might not even be its final form. Please feel free to open an issue if you spot any typos or other problems.
There is a plethora of online examples of nav2, with varying levels of detail and quality. My approach will top-down,
where we will see how illustrative examples and then walk through their contents.
Navigation on a known map#
See also
Official documentation: https://docs.nav2.org/tutorials/docs/navigation2_on_real_turtlebot3.html
In this example, our interest is looking at how navigation can be done with built-in tools, using a known map. A map will have many definitions and the literature about it is extensive. For now, we can think of the map as something usual. It points out where things are, for instance, where passable regions (such as roads) are, and possibly hazards, obstacles, and other relevant objects.
In this example, a TurtleBot3 will be used. As part of nav2_bringup, there
is a rather complete example that we can utilize, namely tb3_simulation_launch.py. The example can be executed
with the following command.
ros2 launch nav2_bringup \
tb3_simulation_launch.py \
use_sim_time:=True \
headless:=False \
sigterm_timeout:=120
Important
Differently from everything else used in this tutorial, this tb3_simulation_launch.py demo does not always finishes
cleanly on my machines. It has hanged sometimes, sometimes Gazebo does not show, and sometimes it does not shutdown properly.
It is highly recommended to use a docker container, such as UoMMScRobotics/sfr_gazebo_nav2, to minimise disappointment.
Warning
The sigterm_timeout flag is particularly important. Given that some devices we use might not be powerful enough
for all processes to finish cleanly within 5 or 10 seconds, we should add this to prevent a SIGTERM from
being sent to the nodes. If nodes are not terminated correctly they can leave connections open, linger indefinitely,
and cause extremely difficult-to-debug situations. This can also be dangerous when real robots are used.
We use headless:=False for the launch file to spawn Gazebo. We use use_sim_time:=True to make sure
that Gazebo's clock will be used an prevent timing issues with /tf.
This example will create a large number of nodes and open two screens. One of these will be for Gazebo and another for rviz2. In this example, there are two actions expected from the user.
Set the initial 2D Pose Estimate. This can be done through rviz2, or ROS2 interfaces.
Send one or more Nav2 Goal s.
An example of how to do so is shown in the video below. Please note that although I adjust Gazebo to line up with the rviz2 view, this is for my convenience. As long as your initial estimate is fairly accurate the navigation should work fine.
Warning
The jazzy version of this example shows a number of errors when shutting down. This might cause issues
when the example is run repeatedly.
Unpacking the example#
Locally, you can navigate to the package using the following command.
cd $(ros2 pkg prefix nav2_bringup --share)
The installed folder will have the following structure.
nav2_bringup/
├── cmake
│ ├── nav2_bringupConfig.cmake
│ └── nav2_bringupConfig-version.cmake
├── environment
│ ├── ament_prefix_path.dsv
│ ├── ament_prefix_path.sh
│ ├── path.dsv
│ └── path.sh
├── launch
│ ├── bringup_launch.py
│ ├── cloned_multi_tb3_simulation_launch.py
│ ├── localization_launch.py
│ ├── navigation_launch.py
│ ├── rviz_launch.py
│ ├── slam_launch.py
│ ├── tb3_loopback_simulation.launch.py
│ ├── tb3_simulation_launch.py
│ ├── tb4_loopback_simulation.launch.py
│ ├── tb4_simulation_launch.py
│ └── unique_multi_tb3_simulation_launch.py
├── local_setup.bash
├── local_setup.dsv
├── local_setup.sh
├── local_setup.zsh
├── maps
│ ├── depot.pgm
│ ├── depot.yaml
│ ├── tb3_sandbox.pgm
│ ├── tb3_sandbox.yaml
│ ├── warehouse.pgm
│ └── warehouse.yaml
├── package.dsv
├── package.xml
├── params
│ ├── nav2_multirobot_params_1.yaml
│ ├── nav2_multirobot_params_2.yaml
│ ├── nav2_multirobot_params_all.yaml
│ └── nav2_params.yaml
└── rviz
├── nav2_default_view.rviz
└── nav2_namespaced_view.rviz
The launch file we executed in this example is shown below. A launch file that is general will tend to have a proportional level of complexity. We are not currently interested in dissecting all elements of this file. Rather, our interest is to take a look at the relevant files used and what they mean. By understanding these, we would be a step closer to be able to modify the example or interact with it for your own purposes.
Contents of tb3_simulation_launch.py
1# Copyright (C) 2023 Open Source Robotics Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""This is all-in-one launch script intended for use by nav2 developers."""
16
17import os
18import tempfile
19
20from ament_index_python.packages import get_package_share_directory
21
22from launch import LaunchDescription
23from launch.actions import (
24 DeclareLaunchArgument,
25 ExecuteProcess,
26 IncludeLaunchDescription,
27 OpaqueFunction,
28 RegisterEventHandler,
29)
30from launch.conditions import IfCondition
31from launch.event_handlers import OnShutdown
32from launch.launch_description_sources import PythonLaunchDescriptionSource
33from launch.substitutions import LaunchConfiguration, PythonExpression
34
35from launch_ros.actions import Node
36
37
38def generate_launch_description():
39 # Get the launch directory
40 bringup_dir = get_package_share_directory('nav2_bringup')
41 launch_dir = os.path.join(bringup_dir, 'launch')
42 sim_dir = get_package_share_directory('nav2_minimal_tb3_sim')
43
44 # Create the launch configuration variables
45 slam = LaunchConfiguration('slam')
46 namespace = LaunchConfiguration('namespace')
47 use_namespace = LaunchConfiguration('use_namespace')
48 map_yaml_file = LaunchConfiguration('map')
49 use_sim_time = LaunchConfiguration('use_sim_time')
50 params_file = LaunchConfiguration('params_file')
51 autostart = LaunchConfiguration('autostart')
52 use_composition = LaunchConfiguration('use_composition')
53 use_respawn = LaunchConfiguration('use_respawn')
54
55 # Launch configuration variables specific to simulation
56 rviz_config_file = LaunchConfiguration('rviz_config_file')
57 use_simulator = LaunchConfiguration('use_simulator')
58 use_robot_state_pub = LaunchConfiguration('use_robot_state_pub')
59 use_rviz = LaunchConfiguration('use_rviz')
60 headless = LaunchConfiguration('headless')
61 world = LaunchConfiguration('world')
62 pose = {
63 'x': LaunchConfiguration('x_pose', default='-2.00'),
64 'y': LaunchConfiguration('y_pose', default='-0.50'),
65 'z': LaunchConfiguration('z_pose', default='0.01'),
66 'R': LaunchConfiguration('roll', default='0.00'),
67 'P': LaunchConfiguration('pitch', default='0.00'),
68 'Y': LaunchConfiguration('yaw', default='0.00'),
69 }
70 robot_name = LaunchConfiguration('robot_name')
71 robot_sdf = LaunchConfiguration('robot_sdf')
72
73 remappings = [('/tf', 'tf'), ('/tf_static', 'tf_static')]
74
75 # Declare the launch arguments
76 declare_namespace_cmd = DeclareLaunchArgument(
77 'namespace', default_value='', description='Top-level namespace'
78 )
79
80 declare_use_namespace_cmd = DeclareLaunchArgument(
81 'use_namespace',
82 default_value='false',
83 description='Whether to apply a namespace to the navigation stack',
84 )
85
86 declare_slam_cmd = DeclareLaunchArgument(
87 'slam', default_value='False', description='Whether run a SLAM'
88 )
89
90 declare_map_yaml_cmd = DeclareLaunchArgument(
91 'map',
92 default_value=os.path.join(bringup_dir, 'maps', 'tb3_sandbox.yaml'),
93 )
94
95 declare_use_sim_time_cmd = DeclareLaunchArgument(
96 'use_sim_time',
97 default_value='true',
98 description='Use simulation (Gazebo) clock if true',
99 )
100
101 declare_params_file_cmd = DeclareLaunchArgument(
102 'params_file',
103 default_value=os.path.join(bringup_dir, 'params', 'nav2_params.yaml'),
104 description='Full path to the ROS2 parameters file to use for all launched nodes',
105 )
106
107 declare_autostart_cmd = DeclareLaunchArgument(
108 'autostart',
109 default_value='true',
110 description='Automatically startup the nav2 stack',
111 )
112
113 declare_use_composition_cmd = DeclareLaunchArgument(
114 'use_composition',
115 default_value='True',
116 description='Whether to use composed bringup',
117 )
118
119 declare_use_respawn_cmd = DeclareLaunchArgument(
120 'use_respawn',
121 default_value='False',
122 description='Whether to respawn if a node crashes. Applied when composition is disabled.',
123 )
124
125 declare_rviz_config_file_cmd = DeclareLaunchArgument(
126 'rviz_config_file',
127 default_value=os.path.join(bringup_dir, 'rviz', 'nav2_default_view.rviz'),
128 description='Full path to the RVIZ config file to use',
129 )
130
131 declare_use_simulator_cmd = DeclareLaunchArgument(
132 'use_simulator',
133 default_value='True',
134 description='Whether to start the simulator',
135 )
136
137 declare_use_robot_state_pub_cmd = DeclareLaunchArgument(
138 'use_robot_state_pub',
139 default_value='True',
140 description='Whether to start the robot state publisher',
141 )
142
143 declare_use_rviz_cmd = DeclareLaunchArgument(
144 'use_rviz', default_value='True', description='Whether to start RVIZ'
145 )
146
147 declare_simulator_cmd = DeclareLaunchArgument(
148 'headless', default_value='True', description='Whether to execute gzclient)'
149 )
150
151 declare_world_cmd = DeclareLaunchArgument(
152 'world',
153 default_value=os.path.join(sim_dir, 'worlds', 'tb3_sandbox.sdf.xacro'),
154 description='Full path to world model file to load',
155 )
156
157 declare_robot_name_cmd = DeclareLaunchArgument(
158 'robot_name', default_value='turtlebot3_waffle', description='name of the robot'
159 )
160
161 declare_robot_sdf_cmd = DeclareLaunchArgument(
162 'robot_sdf',
163 default_value=os.path.join(sim_dir, 'urdf', 'gz_waffle.sdf.xacro'),
164 description='Full path to robot sdf file to spawn the robot in gazebo',
165 )
166
167 urdf = os.path.join(sim_dir, 'urdf', 'turtlebot3_waffle.urdf')
168 with open(urdf, 'r') as infp:
169 robot_description = infp.read()
170
171 start_robot_state_publisher_cmd = Node(
172 condition=IfCondition(use_robot_state_pub),
173 package='robot_state_publisher',
174 executable='robot_state_publisher',
175 name='robot_state_publisher',
176 namespace=namespace,
177 output='screen',
178 parameters=[
179 {'use_sim_time': use_sim_time, 'robot_description': robot_description}
180 ],
181 remappings=remappings,
182 )
183
184 rviz_cmd = IncludeLaunchDescription(
185 PythonLaunchDescriptionSource(os.path.join(launch_dir, 'rviz_launch.py')),
186 condition=IfCondition(use_rviz),
187 launch_arguments={
188 'namespace': namespace,
189 'use_namespace': use_namespace,
190 'use_sim_time': use_sim_time,
191 'rviz_config': rviz_config_file,
192 }.items(),
193 )
194
195 bringup_cmd = IncludeLaunchDescription(
196 PythonLaunchDescriptionSource(os.path.join(launch_dir, 'bringup_launch.py')),
197 launch_arguments={
198 'namespace': namespace,
199 'use_namespace': use_namespace,
200 'slam': slam,
201 'map': map_yaml_file,
202 'use_sim_time': use_sim_time,
203 'params_file': params_file,
204 'autostart': autostart,
205 'use_composition': use_composition,
206 'use_respawn': use_respawn,
207 }.items(),
208 )
209 # The SDF file for the world is a xacro file because we wanted to
210 # conditionally load the SceneBroadcaster plugin based on wheter we're
211 # running in headless mode. But currently, the Gazebo command line doesn't
212 # take SDF strings for worlds, so the output of xacro needs to be saved into
213 # a temporary file and passed to Gazebo.
214 world_sdf = tempfile.mktemp(prefix='nav2_', suffix='.sdf')
215 world_sdf_xacro = ExecuteProcess(
216 cmd=['xacro', '-o', world_sdf, ['headless:=', headless], world])
217 gazebo_server = ExecuteProcess(
218 cmd=['gz', 'sim', '-r', '-s', world_sdf],
219 output='screen',
220 condition=IfCondition(use_simulator)
221 )
222
223 remove_temp_sdf_file = RegisterEventHandler(event_handler=OnShutdown(
224 on_shutdown=[
225 OpaqueFunction(function=lambda _: os.remove(world_sdf))
226 ]))
227
228 gazebo_client = IncludeLaunchDescription(
229 PythonLaunchDescriptionSource(
230 os.path.join(get_package_share_directory('ros_gz_sim'),
231 'launch',
232 'gz_sim.launch.py')
233 ),
234 condition=IfCondition(PythonExpression(
235 [use_simulator, ' and not ', headless])),
236 launch_arguments={'gz_args': ['-v4 -g ']}.items(),
237 )
238
239 gz_robot = IncludeLaunchDescription(
240 PythonLaunchDescriptionSource(
241 os.path.join(sim_dir, 'launch', 'spawn_tb3.launch.py')),
242 launch_arguments={'namespace': namespace,
243 'use_sim_time': use_sim_time,
244 'robot_name': robot_name,
245 'robot_sdf': robot_sdf,
246 'x_pose': pose['x'],
247 'y_pose': pose['y'],
248 'z_pose': pose['z'],
249 'roll': pose['R'],
250 'pitch': pose['P'],
251 'yaw': pose['Y']}.items())
252
253 # Create the launch description and populate
254 ld = LaunchDescription()
255
256 # Declare the launch options
257 ld.add_action(declare_namespace_cmd)
258 ld.add_action(declare_use_namespace_cmd)
259 ld.add_action(declare_slam_cmd)
260 ld.add_action(declare_map_yaml_cmd)
261 ld.add_action(declare_use_sim_time_cmd)
262 ld.add_action(declare_params_file_cmd)
263 ld.add_action(declare_autostart_cmd)
264 ld.add_action(declare_use_composition_cmd)
265
266 ld.add_action(declare_rviz_config_file_cmd)
267 ld.add_action(declare_use_simulator_cmd)
268 ld.add_action(declare_use_robot_state_pub_cmd)
269 ld.add_action(declare_use_rviz_cmd)
270 ld.add_action(declare_simulator_cmd)
271 ld.add_action(declare_world_cmd)
272 ld.add_action(declare_robot_name_cmd)
273 ld.add_action(declare_robot_sdf_cmd)
274 ld.add_action(declare_use_respawn_cmd)
275
276 ld.add_action(world_sdf_xacro)
277 ld.add_action(remove_temp_sdf_file)
278 ld.add_action(gz_robot)
279 ld.add_action(gazebo_server)
280 ld.add_action(gazebo_client)
281
282 # Add the actions to launch all of the navigation nodes
283 ld.add_action(start_robot_state_publisher_cmd)
284 ld.add_action(rviz_cmd)
285 ld.add_action(bringup_cmd)
286
287 return ld
The highlighted lines point out to these important files. These will guide the upcoming discussion.
params/nav2_params.yamlmaps/tb3_sandbox.yamlworlds/tb3_sandbox.sdf.xacrourdf/gz_waffle.sdf.xacrourdf/turtlebot3_waffle.urdf
Navigation#
See also
Official documentation: https://docs.nav2.org/configuration/index.html
In the example, the navigation is defined in the following configuration file.
Contents of nav2_params.yaml
1amcl:
2 ros__parameters:
3 alpha1: 0.2
4 alpha2: 0.2
5 alpha3: 0.2
6 alpha4: 0.2
7 alpha5: 0.2
8 base_frame_id: "base_footprint"
9 beam_skip_distance: 0.5
10 beam_skip_error_threshold: 0.9
11 beam_skip_threshold: 0.3
12 do_beamskip: false
13 global_frame_id: "map"
14 lambda_short: 0.1
15 laser_likelihood_max_dist: 2.0
16 laser_max_range: 100.0
17 laser_min_range: -1.0
18 laser_model_type: "likelihood_field"
19 max_beams: 60
20 max_particles: 2000
21 min_particles: 500
22 odom_frame_id: "odom"
23 pf_err: 0.05
24 pf_z: 0.99
25 recovery_alpha_fast: 0.0
26 recovery_alpha_slow: 0.0
27 resample_interval: 1
28 robot_model_type: "nav2_amcl::DifferentialMotionModel"
29 save_pose_rate: 0.5
30 sigma_hit: 0.2
31 tf_broadcast: true
32 transform_tolerance: 1.0
33 update_min_a: 0.2
34 update_min_d: 0.25
35 z_hit: 0.5
36 z_max: 0.05
37 z_rand: 0.5
38 z_short: 0.05
39 scan_topic: scan
40
41bt_navigator:
42 ros__parameters:
43 global_frame: map
44 robot_base_frame: base_link
45 odom_topic: /odom
46 bt_loop_duration: 10
47 default_server_timeout: 20
48 wait_for_service_timeout: 1000
49 action_server_result_timeout: 900.0
50 navigators: ["navigate_to_pose", "navigate_through_poses"]
51 navigate_to_pose:
52 plugin: "nav2_bt_navigator::NavigateToPoseNavigator"
53 navigate_through_poses:
54 plugin: "nav2_bt_navigator::NavigateThroughPosesNavigator"
55 # 'default_nav_through_poses_bt_xml' and 'default_nav_to_pose_bt_xml' are use defaults:
56 # nav2_bt_navigator/navigate_to_pose_w_replanning_and_recovery.xml
57 # nav2_bt_navigator/navigate_through_poses_w_replanning_and_recovery.xml
58 # They can be set here or via a RewrittenYaml remap from a parent launch file to Nav2.
59
60 # plugin_lib_names is used to add custom BT plugins to the executor (vector of strings).
61 # Built-in plugins are added automatically
62 # plugin_lib_names: []
63
64 error_code_names:
65 - compute_path_error_code
66 - follow_path_error_code
67
68controller_server:
69 ros__parameters:
70 controller_frequency: 20.0
71 costmap_update_timeout: 0.30
72 min_x_velocity_threshold: 0.001
73 min_y_velocity_threshold: 0.5
74 min_theta_velocity_threshold: 0.001
75 failure_tolerance: 0.3
76 progress_checker_plugins: ["progress_checker"]
77 goal_checker_plugins: ["general_goal_checker"] # "precise_goal_checker"
78 controller_plugins: ["FollowPath"]
79 use_realtime_priority: false
80
81 # Progress checker parameters
82 progress_checker:
83 plugin: "nav2_controller::SimpleProgressChecker"
84 required_movement_radius: 0.5
85 movement_time_allowance: 10.0
86 # Goal checker parameters
87 #precise_goal_checker:
88 # plugin: "nav2_controller::SimpleGoalChecker"
89 # xy_goal_tolerance: 0.25
90 # yaw_goal_tolerance: 0.25
91 # stateful: True
92 general_goal_checker:
93 stateful: True
94 plugin: "nav2_controller::SimpleGoalChecker"
95 xy_goal_tolerance: 0.25
96 yaw_goal_tolerance: 0.25
97 FollowPath:
98 plugin: "nav2_mppi_controller::MPPIController"
99 time_steps: 56
100 model_dt: 0.05
101 batch_size: 2000
102 ax_max: 3.0
103 ax_min: -3.0
104 ay_max: 3.0
105 ay_min: -3.0
106 az_max: 3.5
107 vx_std: 0.2
108 vy_std: 0.2
109 wz_std: 0.4
110 vx_max: 0.5
111 vx_min: -0.35
112 vy_max: 0.5
113 wz_max: 1.9
114 iteration_count: 1
115 prune_distance: 1.7
116 transform_tolerance: 0.1
117 temperature: 0.3
118 gamma: 0.015
119 motion_model: "DiffDrive"
120 visualize: true
121 regenerate_noises: true
122 TrajectoryVisualizer:
123 trajectory_step: 5
124 time_step: 3
125 AckermannConstraints:
126 min_turning_r: 0.2
127 critics: [
128 "ConstraintCritic", "CostCritic", "GoalCritic",
129 "GoalAngleCritic", "PathAlignCritic", "PathFollowCritic",
130 "PathAngleCritic", "PreferForwardCritic"]
131 ConstraintCritic:
132 enabled: true
133 cost_power: 1
134 cost_weight: 4.0
135 GoalCritic:
136 enabled: true
137 cost_power: 1
138 cost_weight: 5.0
139 threshold_to_consider: 1.4
140 GoalAngleCritic:
141 enabled: true
142 cost_power: 1
143 cost_weight: 3.0
144 threshold_to_consider: 0.5
145 PreferForwardCritic:
146 enabled: true
147 cost_power: 1
148 cost_weight: 5.0
149 threshold_to_consider: 0.5
150 CostCritic:
151 enabled: true
152 cost_power: 1
153 cost_weight: 3.81
154 near_collision_cost: 253
155 critical_cost: 300.0
156 consider_footprint: false
157 collision_cost: 1000000.0
158 near_goal_distance: 1.0
159 trajectory_point_step: 2
160 PathAlignCritic:
161 enabled: true
162 cost_power: 1
163 cost_weight: 14.0
164 max_path_occupancy_ratio: 0.05
165 trajectory_point_step: 4
166 threshold_to_consider: 0.5
167 offset_from_furthest: 20
168 use_path_orientations: false
169 PathFollowCritic:
170 enabled: true
171 cost_power: 1
172 cost_weight: 5.0
173 offset_from_furthest: 5
174 threshold_to_consider: 1.4
175 PathAngleCritic:
176 enabled: true
177 cost_power: 1
178 cost_weight: 2.0
179 offset_from_furthest: 4
180 threshold_to_consider: 0.5
181 max_angle_to_furthest: 1.0
182 mode: 0
183 # TwirlingCritic:
184 # enabled: true
185 # twirling_cost_power: 1
186 # twirling_cost_weight: 10.0
187
188local_costmap:
189 local_costmap:
190 ros__parameters:
191 update_frequency: 5.0
192 publish_frequency: 2.0
193 global_frame: odom
194 robot_base_frame: base_link
195 rolling_window: true
196 width: 3
197 height: 3
198 resolution: 0.05
199 robot_radius: 0.22
200 plugins: ["voxel_layer", "inflation_layer"]
201 inflation_layer:
202 plugin: "nav2_costmap_2d::InflationLayer"
203 cost_scaling_factor: 3.0
204 inflation_radius: 0.70
205 voxel_layer:
206 plugin: "nav2_costmap_2d::VoxelLayer"
207 enabled: True
208 publish_voxel_map: True
209 origin_z: 0.0
210 z_resolution: 0.05
211 z_voxels: 16
212 max_obstacle_height: 2.0
213 mark_threshold: 0
214 observation_sources: scan
215 scan:
216 topic: /scan
217 max_obstacle_height: 2.0
218 clearing: True
219 marking: True
220 data_type: "LaserScan"
221 raytrace_max_range: 3.0
222 raytrace_min_range: 0.0
223 obstacle_max_range: 2.5
224 obstacle_min_range: 0.0
225 static_layer:
226 plugin: "nav2_costmap_2d::StaticLayer"
227 map_subscribe_transient_local: True
228 always_send_full_costmap: True
229
230global_costmap:
231 global_costmap:
232 ros__parameters:
233 update_frequency: 1.0
234 publish_frequency: 1.0
235 global_frame: map
236 robot_base_frame: base_link
237 robot_radius: 0.22
238 resolution: 0.05
239 track_unknown_space: true
240 plugins: ["static_layer", "obstacle_layer", "inflation_layer"]
241 obstacle_layer:
242 plugin: "nav2_costmap_2d::ObstacleLayer"
243 enabled: True
244 observation_sources: scan
245 scan:
246 topic: /scan
247 max_obstacle_height: 2.0
248 clearing: True
249 marking: True
250 data_type: "LaserScan"
251 raytrace_max_range: 3.0
252 raytrace_min_range: 0.0
253 obstacle_max_range: 2.5
254 obstacle_min_range: 0.0
255 static_layer:
256 plugin: "nav2_costmap_2d::StaticLayer"
257 map_subscribe_transient_local: True
258 inflation_layer:
259 plugin: "nav2_costmap_2d::InflationLayer"
260 cost_scaling_factor: 3.0
261 inflation_radius: 0.7
262 always_send_full_costmap: True
263
264# The yaml_filename does not need to be specified since it going to be set by defaults in launch.
265# If you'd rather set it in the yaml, remove the default "map" value in the tb3_simulation_launch.py
266# file & provide full path to map below. If CLI map configuration or launch default is provided, that will be used.
267# map_server:
268# ros__parameters:
269# yaml_filename: ""
270
271map_saver:
272 ros__parameters:
273 save_map_timeout: 5.0
274 free_thresh_default: 0.25
275 occupied_thresh_default: 0.65
276 map_subscribe_transient_local: True
277
278planner_server:
279 ros__parameters:
280 expected_planner_frequency: 20.0
281 planner_plugins: ["GridBased"]
282 costmap_update_timeout: 1.0
283 GridBased:
284 plugin: "nav2_navfn_planner::NavfnPlanner"
285 tolerance: 0.5
286 use_astar: false
287 allow_unknown: true
288
289smoother_server:
290 ros__parameters:
291 smoother_plugins: ["simple_smoother"]
292 simple_smoother:
293 plugin: "nav2_smoother::SimpleSmoother"
294 tolerance: 1.0e-10
295 max_its: 1000
296 do_refinement: True
297
298behavior_server:
299 ros__parameters:
300 local_costmap_topic: local_costmap/costmap_raw
301 global_costmap_topic: global_costmap/costmap_raw
302 local_footprint_topic: local_costmap/published_footprint
303 global_footprint_topic: global_costmap/published_footprint
304 cycle_frequency: 10.0
305 behavior_plugins: ["spin", "backup", "drive_on_heading", "assisted_teleop", "wait"]
306 spin:
307 plugin: "nav2_behaviors::Spin"
308 backup:
309 plugin: "nav2_behaviors::BackUp"
310 drive_on_heading:
311 plugin: "nav2_behaviors::DriveOnHeading"
312 wait:
313 plugin: "nav2_behaviors::Wait"
314 assisted_teleop:
315 plugin: "nav2_behaviors::AssistedTeleop"
316 local_frame: odom
317 global_frame: map
318 robot_base_frame: base_link
319 transform_tolerance: 0.1
320 simulate_ahead_time: 2.0
321 max_rotational_vel: 1.0
322 min_rotational_vel: 0.4
323 rotational_acc_lim: 3.2
324
325waypoint_follower:
326 ros__parameters:
327 loop_rate: 20
328 stop_on_failure: false
329 action_server_result_timeout: 900.0
330 waypoint_task_executor_plugin: "wait_at_waypoint"
331 wait_at_waypoint:
332 plugin: "nav2_waypoint_follower::WaitAtWaypoint"
333 enabled: True
334 waypoint_pause_duration: 200
335
336route_server:
337 ros__parameters:
338 # The graph_filepath does not need to be specified since it going to be set by defaults in launch.
339 # If you'd rather set it in the yaml, remove the default "graph" value in the launch file(s).
340 # file & provide full path to map below. If graph config or launch default is provided, it is used
341 # graph_filepath: $(find-pkg-share nav2_route)/graphs/aws_graph.geojson
342 boundary_radius_to_achieve_node: 1.0
343 radius_to_achieve_node: 2.0
344 smooth_corners: true
345 operations: ["AdjustSpeedLimit", "ReroutingService", "CollisionMonitor"]
346 ReroutingService:
347 plugin: "nav2_route::ReroutingService"
348 AdjustSpeedLimit:
349 plugin: "nav2_route::AdjustSpeedLimit"
350 CollisionMonitor:
351 plugin: "nav2_route::CollisionMonitor"
352 max_collision_dist: 3.0
353 edge_cost_functions: ["DistanceScorer", "CostmapScorer"]
354 DistanceScorer:
355 plugin: "nav2_route::DistanceScorer"
356 CostmapScorer:
357 plugin: "nav2_route::CostmapScorer"
358
359velocity_smoother:
360 ros__parameters:
361 smoothing_frequency: 20.0
362 stamp_smoothed_velocity_with_smoothing_time: False
363 scale_velocities: False
364 feedback: "OPEN_LOOP"
365 max_velocity: [0.5, 0.0, 2.0]
366 min_velocity: [-0.5, 0.0, -2.0]
367 max_accel: [2.5, 0.0, 3.2]
368 max_decel: [-2.5, 0.0, -3.2]
369 odom_topic: "odom"
370 odom_duration: 0.1
371 deadband_velocity: [0.0, 0.0, 0.0]
372 velocity_timeout: 1.0
373
374collision_monitor:
375 ros__parameters:
376 base_frame_id: "base_footprint"
377 odom_frame_id: "odom"
378 cmd_vel_in_topic: "cmd_vel_smoothed"
379 cmd_vel_out_topic: "cmd_vel"
380 state_topic: "collision_monitor_state"
381 transform_tolerance: 0.2
382 source_timeout: 1.0
383 base_shift_correction: True
384 stop_pub_timeout: 2.0
385 # Polygons represent zone around the robot for "stop", "slowdown" and "limit" action types,
386 # and robot footprint for "approach" action type.
387 polygons: ["FootprintApproach"]
388 FootprintApproach:
389 type: "polygon"
390 action_type: "approach"
391 footprint_topic: "/local_costmap/published_footprint"
392 time_before_collision: 1.2
393 simulation_time_step: 0.1
394 min_points: 6
395 visualize: False
396 enabled: True
397 observation_sources: ["scan"]
398 scan:
399 type: "scan"
400 topic: "scan"
401 min_height: 0.15
402 max_height: 2.0
403 enabled: True
404
405docking_server:
406 ros__parameters:
407 controller_frequency: 50.0
408 initial_perception_timeout: 5.0
409 wait_charge_timeout: 5.0
410 dock_approach_timeout: 30.0
411 undock_linear_tolerance: 0.05
412 undock_angular_tolerance: 0.1
413 max_retries: 3
414 base_frame: "base_link"
415 fixed_frame: "odom"
416 dock_backwards: false
417 dock_prestaging_tolerance: 0.5
418
419 # Types of docks
420 dock_plugins: ['simple_charging_dock']
421 simple_charging_dock:
422 plugin: 'opennav_docking::SimpleChargingDock'
423 docking_threshold: 0.05
424 staging_x_offset: -0.7
425 use_external_detection_pose: true
426 use_battery_status: false # true
427 use_stall_detection: false # true
428
429 external_detection_timeout: 1.0
430 external_detection_translation_x: -0.18
431 external_detection_translation_y: 0.0
432 external_detection_rotation_roll: -1.57
433 external_detection_rotation_pitch: -1.57
434 external_detection_rotation_yaw: 0.0
435 filter_coef: 0.1
436
437 # Dock instances
438 # The following example illustrates configuring dock instances.
439 # docks: ['home_dock'] # Input your docks here
440 # home_dock:
441 # type: 'simple_charging_dock'
442 # frame: map
443 # pose: [0.0, 0.0, 0.0]
444
445 controller:
446 k_phi: 3.0
447 k_delta: 2.0
448 v_linear_min: 0.15
449 v_linear_max: 0.15
450 use_collision_detection: true
451 costmap_topic: "local_costmap/costmap_raw"
452 footprint_topic: "local_costmap/published_footprint"
453 transform_tolerance: 0.1
454 projection_time: 5.0
455 simulation_step: 0.1
456 dock_collision_threshold: 0.3
457
458loopback_simulator:
459 ros__parameters:
460 base_frame_id: "base_footprint"
461 odom_frame_id: "odom"
462 map_frame_id: "map"
463 scan_frame_id: "base_scan" # tb4_loopback_simulator.launch.py remaps to 'rplidar_link'
464 update_duration: 0.02
465 scan_range_min: 0.05
466 scan_range_max: 30.0
467 scan_angle_min: -3.1415
468 scan_angle_max: 3.1415
469 scan_angle_increment: 0.02617
470 scan_use_inf: true
Once more, there is a trade-off between generality and the complexity of the configuration file. We can unpack the major elements of the configuration in the table below.
In nav2, each functionality is divided into a so-called server. Each server will have its own set of parameters and
will communicate with other servers. In this topology, ideally one functionality can be modified without affecting
other servers. These are not all servers available in nav2, but all the servers used in this example.
Server |
TL;DR |
Link |
|---|---|---|
|
Adaptive Monte-Carlo Localiser |
|
|
Behavior tree navigator |
|
|
Controller server |
|
|
A 2D costmap |
|
|
A 2D costmap |
|
|
Calculates path to goal |
|
|
Keeps path smooth |
|
|
Defines basic robot behaviors |
|
|
Handles pre-defined routes |
|
|
Smooth velocities sent to robots |
|
|
Extra layer of safety |
Each server can be attached to one or more nav2 plugins. The plugins will define important aspects of the
server and plugins will have their own parameters.
See also
Official documentation: https://docs.nav2.org/plugins/index.html
In nav2, mainly, there will be a global planner (in this case nav2_navfn_planner::NavfnPlanner) and a local
controller (in this case nav2_mppi_controller::MPPIController). In general terms, the global planner can find solutions
when the goal is far and create a trajectory. The controller will be responsible for dealing with following the trajectory
in the short term. This split is common in robotics. Both will use costmaps, described in a following section.
The trajectories generated by the global planner might not be smooth owing to several factors, including the parameters
used to adjust execution speed. This possible jaggedness can be countered with a smoother_server. Analogously, the
velocity calculated by the controller might not be smooth and, if applied directly, cause the robot to vibrate, destabilise,
wear, or break. The velocity_smoother should help to address this.
Lastly, the behavior_server will describe basic robot behaviours such as Wait, or Move Straight for a given robot.
The bt_navigator will utilise these, among other things, to decide higher level behaviour beyond the planner. A
behaviour tree can be seen as a more general state machine. Thence, as the name implies, each behaviour can branch into
different outcomes defined for each behaviour. For instance, if the robot fails reaching a goal, it can be set to do another
task or attempt to replan to another location.
map#
The contents of tb3_sandbox.yaml are shown below.
1image: tb3_sandbox.pgm
2resolution: 0.050000
3origin: [-10.000000, -10.000000, 0.000000]
4negate: 0
5occupied_thresh: 0.65
6free_thresh: 0.196
The map will have several parameters. The global costmap is defined in tb3_sandbox.pgm.
A costmap in nav2 is a 2D grid in which each cell is assigned a value, similarly to an image. Using the image analogy,
each costmap (image) will be made of costs (pixels). The value in each pixel will define how costly it is to move
through that pixel.
Suppose that we have the small costmap below, for illustrative purposes. Suppose that we use F for free space, U
for unknown, and O for occupied. We can see this as a top-view image of the path that the robot can traverse. The planner
and controller will use this information to define how and where to navigate.
O |
O |
O |
O |
U |
O |
|
|
O |
U |
O |
O |
|
O |
U |
O |
|
|
O |
U |
O |
O |
O |
O |
U |
For larger maps, and to consolidate the image analogy, this is one possible representation of tb3_sandbox.pgm. In this
representation, the dark pixels represent occupied regions, such as walls. The white pixel represents free movement. The
grey pixels represent unknown regions. This will be part of the so-called nav2_costmap_2d::StaticLayer, representing
these three states.
Below is a representation of tb3_sandbox.pgm when opened in an image viewer.
Given that we are unable to fully entrust the safety of the robot to our carefully-drawn maps, nav2 also allows the use
of a nav2_costmap_2d::ObstacleLayer. This will be tied to one of the robot sensors, for instance, a laser scanner.
This will allow the robot to avoid obstacles when the map itself was inaccurate, for instance, if an obstacle has not
been accounted for, or account for inaccuracies in the localisation itself. The nav2_costmap_2d::VoxelLayer is used
in this example for a similar purpose, with the difference that the sensor relays 3D information that is then transformed
into relevant 2D information.
Lastly, there will be the nav2_costmap_2d::InflationLayer. Given that the maps hold only three possible states for
each pixel, the changes are sudden. The inflation layer will behave similarly to a smoother. The region around each occupied
cell in the grid will be modified to show that, although that is indeed free space, the robot should not be going through
there. By assigning these costs when approaching an obstacle, planners and controllers can devise better trajectories.
The navigation file also had a local costmap, which works similarly, but can have different parameters and accept data from different sources. The reason for this is that it is more convenient to have individual parameters for the planner and the controller.
World and robot definition files#
As you might remember from the .sdf contents, they are XML files. These files, by default, do not take advantage
of programming concepts, such as macros and conditionals. This becomes an issue when you want to make a robot description
file that is slightly more general and can be easily modified with specific parameters. Therefore you are likely to
see xacro being used in many ROS examples.
For instance, the tb3_sandbox.sdf.xacro file’s contents can be modified by the parameter headless. Where
the parameter is defined and where it’s used are highlighted in the file below. In this example, this might be a quick
patch given that Gazebo does not seem to support it and xacro will pre-process
the file to output a compliant .sdf based on the parameter value.
The contents of tb3_sandbox.sdf.xacro.
1<?xml version="1.0"?>
2<sdf version="1.6" xmlns:xacro="http://www.ros.org/wiki/xacro">
3 <xacro:arg name="headless" default="true"/>
4
5 <world name="default">
6 <plugin
7 filename="gz-sim-physics-system"
8 name="gz::sim::systems::Physics">
9 </plugin>
10 <plugin
11 filename="gz-sim-user-commands-system"
12 name="gz::sim::systems::UserCommands">
13 </plugin>
14 <xacro:unless value="$(arg headless)">
15 <plugin
16 filename="gz-sim-scene-broadcaster-system"
17 name="gz::sim::systems::SceneBroadcaster">
18 </plugin>
19 </xacro:unless>
20 <plugin
21 filename="gz-sim-sensors-system"
22 name="gz::sim::systems::Sensors">
23 <render_engine>ogre2</render_engine>
24 </plugin>
25 <plugin
26 filename="gz-sim-imu-system"
27 name="gz::sim::systems::Imu">
28 </plugin>
29
30 <light name='sun' type='directional'>
31 <cast_shadows>0</cast_shadows>
32 <pose>0 0 10 0 0 0</pose>
33 <diffuse>0.8 0.8 0.8 1</diffuse>
34 <specular>0.8 0.8 0.8 1</specular>
35 <attenuation>
36 <range>1000</range>
37 <constant>0.9</constant>
38 <linear>0.01</linear>
39 <quadratic>0.001</quadratic>
40 </attenuation>
41 <direction>-0.5 0.1 -0.9</direction>
42 </light>
43 <model name='ground_plane'>
44 <static>1</static>
45 <link name='link'>
46 <collision name='collision'>
47 <geometry>
48 <plane>
49 <normal>0 0 1</normal>
50 <size>100 100</size>
51 </plane>
52 </geometry>
53 <max_contacts>10</max_contacts>
54 </collision>
55 <visual name='visual'>
56 <geometry>
57 <plane>
58 <normal>0 0 1</normal>
59 <size>100 100</size>
60 </plane>
61 </geometry>
62 <material>
63 <ambient>0.8 0.8 0.8 1</ambient>
64 <diffuse>0.8 0.8 0.8 1</diffuse>
65 <specular>0.8 0.8 0.8 1</specular>
66 </material>
67 </visual>
68 </link>
69 </model>
70
71 <scene>
72 <shadows>0</shadows>
73 </scene>
74
75 <physics name='3ms' type='ode'>
76 <max_step_size>0.003</max_step_size>
77 <real_time_factor>1</real_time_factor>
78 </physics>
79
80 <model name="turtlebot3_world">
81 <static>1</static>
82 <include>
83 <uri>model://turtlebot3_world</uri>
84 </include>
85 </model>
86
87 </world>
88</sdf>
Something similar is done to allow Gazebo topics to have a different namespace. This is helpful when multiple robots are added to the same Gazebo world.
The contents of gz_waffle.sdf.xacro.
1<?xml version="1.0"?>
2<sdf version="1.6" xmlns:xacro="http://www.ros.org/wiki/xacro">
3 <xacro:arg name="namespace" default=""/>
4
5 <model name="turtlebot3_waffle">
6 <pose>0.0 0.0 0.0 0.0 0.0 0.0</pose>
7
8 <link name="base_footprint"/>
9
10 <link name="base_link">
11
12 <inertial>
13 <pose>-0.064 0 0.048 0 0 0</pose>
14 <inertia>
15 <ixx>0.001</ixx>
16 <ixy>0.000</ixy>
17 <ixz>0.000</ixz>
18 <iyy>0.001</iyy>
19 <iyz>0.000</iyz>
20 <izz>0.001</izz>
21 </inertia>
22 <mass>1.0</mass>
23 </inertial>
24
25 <collision name="base_collision">
26 <pose>-0.064 0 0.048 0 0 0</pose>
27 <geometry>
28 <box>
29 <size>0.265 0.265 0.089</size>
30 </box>
31 </geometry>
32 </collision>
33
34 <visual name="base_visual">
35 <pose>-0.064 0 0 0 0 0</pose>
36 <geometry>
37 <mesh>
38 <uri>package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/waffle_base.dae</uri>
39 <scale>0.001 0.001 0.001</scale>
40 </mesh>
41 </geometry>
42 <material>
43 <diffuse>1 1 1</diffuse>
44 </material>
45 </visual>
46 </link>
47
48 <link name="imu_link">
49 <sensor name="tb3_imu" type="imu">
50 <always_on>true</always_on>
51 <update_rate>200</update_rate>
52 <topic>$(arg namespace)/imu</topic>
53 <gz_frame_id>imu_link</gz_frame_id>
54 <imu>
55 <angular_velocity>
56 <x>
57 <noise type="gaussian">
58 <mean>0.0</mean>
59 <stddev>2e-4</stddev>
60 </noise>
61 </x>
62 <y>
63 <noise type="gaussian">
64 <mean>0.0</mean>
65 <stddev>2e-4</stddev>
66 </noise>
67 </y>
68 <z>
69 <noise type="gaussian">
70 <mean>0.0</mean>
71 <stddev>2e-4</stddev>
72 </noise>
73 </z>
74 </angular_velocity>
75 <linear_acceleration>
76 <x>
77 <noise type="gaussian">
78 <mean>0.0</mean>
79 <stddev>1.7e-2</stddev>
80 </noise>
81 </x>
82 <y>
83 <noise type="gaussian">
84 <mean>0.0</mean>
85 <stddev>1.7e-2</stddev>
86 </noise>
87 </y>
88 <z>
89 <noise type="gaussian">
90 <mean>0.0</mean>
91 <stddev>1.7e-2</stddev>
92 </noise>
93 </z>
94 </linear_acceleration>
95 </imu>
96 </sensor>
97 </link>
98
99 <link name="base_scan">
100 <inertial>
101 <pose>-0.064 0 0.121 0 0 0</pose>
102 <inertia>
103 <ixx>0.001</ixx>
104 <ixy>0.000</ixy>
105 <ixz>0.000</ixz>
106 <iyy>0.001</iyy>
107 <iyz>0.000</iyz>
108 <izz>0.001</izz>
109 </inertia>
110 <mass>0.125</mass>
111 </inertial>
112
113 <collision name="lidar_sensor_collision">
114 <pose>-0.052 0 0.111 0 0 0</pose>
115 <geometry>
116 <cylinder>
117 <radius>0.0508</radius>
118 <length>0.055</length>
119 </cylinder>
120 </geometry>
121 </collision>
122
123 <visual name="lidar_sensor_visual">
124 <pose>-0.064 0 0.121 0 0 0</pose>
125 <geometry>
126 <mesh>
127 <uri>package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/lds.dae</uri>
128 <scale>0.001 0.001 0.001</scale>
129 </mesh>
130 </geometry>
131 <material>
132 <diffuse>1 1 1</diffuse>
133 </material>
134 </visual>
135
136 <sensor name="hls_lfcd_lds" type="gpu_lidar">
137 <always_on>true</always_on>
138 <visualize>true</visualize>
139 <pose>-0.064 0 0.15 0 0 0</pose>
140 <update_rate>5</update_rate>
141 <topic>$(arg namespace)/scan</topic>
142 <gz_frame_id>base_scan</gz_frame_id>
143 <ray>
144 <scan>
145 <horizontal>
146 <samples>360</samples>
147 <resolution>1.000000</resolution>
148 <min_angle>0.000000</min_angle>
149 <max_angle>6.280000</max_angle>
150 </horizontal>
151 </scan>
152 <range>
153 <min>0.00001</min>
154 <max>20.0</max>
155 <resolution>0.015000</resolution>
156 </range>
157 <noise>
158 <type>gaussian</type>
159 <mean>0.0</mean>
160 <stddev>0.01</stddev>
161 </noise>
162 </ray>
163 </sensor>
164 </link>
165
166 <link name="wheel_left_link">
167
168 <inertial>
169 <pose>0.0 0.144 0.023 -1.57 0 0</pose>
170 <inertia>
171 <ixx>0.001</ixx>
172 <ixy>0.000</ixy>
173 <ixz>0.000</ixz>
174 <iyy>0.001</iyy>
175 <iyz>0.000</iyz>
176 <izz>0.001</izz>
177 </inertia>
178 <mass>0.1</mass>
179 </inertial>
180
181 <collision name="wheel_left_collision">
182 <pose>0.0 0.144 0.023 -1.57 0 0</pose>
183 <geometry>
184 <cylinder>
185 <radius>0.033</radius>
186 <length>0.018</length>
187 </cylinder>
188 </geometry>
189 <surface>
190 <friction>
191 <ode>
192 <mu>1</mu>
193 <mu2>1</mu2>
194 <slip1>0.035</slip1>
195 <slip2>0</slip2>
196 <fdir1>0 0 1</fdir1>
197 </ode>
198 </friction>
199 <contact>
200 <ode>
201 <soft_cfm>0</soft_cfm>
202 <soft_erp>0.2</soft_erp>
203 <kp>1e+5</kp>
204 <kd>1</kd>
205 <max_vel>0.01</max_vel>
206 <min_depth>0.001</min_depth>
207 </ode>
208 </contact>
209 </surface>
210 </collision>
211
212 <visual name="wheel_left_visual">
213 <pose>0.0 0.144 0.023 0 0 0</pose>
214 <geometry>
215 <mesh>
216 <uri>package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/tire.dae</uri>
217 <scale>0.001 0.001 0.001</scale>
218 </mesh>
219 </geometry>
220 <material>
221 <diffuse>1 1 1</diffuse>
222 </material>
223 </visual>
224 </link>
225
226 <link name="wheel_right_link">
227
228 <inertial>
229 <pose>0.0 -0.144 0.023 -1.57 0 0</pose>
230 <inertia>
231 <ixx>0.001</ixx>
232 <ixy>0.000</ixy>
233 <ixz>0.000</ixz>
234 <iyy>0.001</iyy>
235 <iyz>0.000</iyz>
236 <izz>0.001</izz>
237 </inertia>
238 <mass>0.1</mass>
239 </inertial>
240
241 <collision name="wheel_right_collision">
242 <pose>0.0 -0.144 0.023 -1.57 0 0</pose>
243 <geometry>
244 <cylinder>
245 <radius>0.033</radius>
246 <length>0.018</length>
247 </cylinder>
248 </geometry>
249 <surface>
250 <friction>
251 <ode>
252 <mu>1</mu>
253 <mu2>1</mu2>
254 <slip1>0.035</slip1>
255 <slip2>0</slip2>
256 <fdir1>0 0 1</fdir1>
257 </ode>
258 </friction>
259 <contact>
260 <ode>
261 <soft_cfm>0</soft_cfm>
262 <soft_erp>0.2</soft_erp>
263 <kp>1e+5</kp>
264 <kd>1</kd>
265 <max_vel>0.01</max_vel>
266 <min_depth>0.001</min_depth>
267 </ode>
268 </contact>
269 </surface>
270 </collision>
271
272 <visual name="wheel_right_visual">
273 <pose>0.0 -0.144 0.023 0 0 0</pose>
274 <geometry>
275 <mesh>
276 <uri>package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/tire.dae</uri>
277 <scale>0.001 0.001 0.001</scale>
278 </mesh>
279 </geometry>
280 <material>
281 <diffuse>1 1 1</diffuse>
282 </material>
283 </visual>
284 </link>
285
286 <link name='caster_back_right_link'>
287 <pose>-0.177 -0.064 -0.004 0 0 0</pose>
288 <inertial>
289 <mass>0.001</mass>
290 <inertia>
291 <ixx>0.00001</ixx>
292 <ixy>0.000</ixy>
293 <ixz>0.000</ixz>
294 <iyy>0.00001</iyy>
295 <iyz>0.000</iyz>
296 <izz>0.00001</izz>
297 </inertia>
298 </inertial>
299 <collision name='collision'>
300 <geometry>
301 <sphere>
302 <radius>0.006000</radius>
303 </sphere>
304 </geometry>
305 <surface>
306 <contact>
307 <ode>
308 <soft_cfm>0</soft_cfm>
309 <soft_erp>0.2</soft_erp>
310 <kp>1e+5</kp>
311 <kd>1</kd>
312 <max_vel>0.01</max_vel>
313 <min_depth>0.001</min_depth>
314 </ode>
315 </contact>
316 </surface>
317 </collision>
318 </link>
319
320 <link name='caster_back_left_link'>
321 <pose>-0.177 0.064 -0.004 0 0 0</pose>
322 <inertial>
323 <mass>0.001</mass>
324 <inertia>
325 <ixx>0.00001</ixx>
326 <ixy>0.000</ixy>
327 <ixz>0.000</ixz>
328 <iyy>0.00001</iyy>
329 <iyz>0.000</iyz>
330 <izz>0.00001</izz>
331 </inertia>
332 </inertial>
333 <collision name='collision'>
334 <geometry>
335 <sphere>
336 <radius>0.006000</radius>
337 </sphere>
338 </geometry>
339 <surface>
340 <contact>
341 <ode>
342 <soft_cfm>0</soft_cfm>
343 <soft_erp>0.2</soft_erp>
344 <kp>1e+5</kp>
345 <kd>1</kd>
346 <max_vel>0.01</max_vel>
347 <min_depth>0.001</min_depth>
348 </ode>
349 </contact>
350 </surface>
351 </collision>
352 </link>
353
354 <link name="camera_link">
355 <inertial>
356 <pose>0.069 -0.047 0.107 0 0 0</pose>
357 <inertia>
358 <ixx>0.001</ixx>
359 <ixy>0.000</ixy>
360 <ixz>0.000</ixz>
361 <iyy>0.001</iyy>
362 <iyz>0.000</iyz>
363 <izz>0.001</izz>
364 </inertia>
365 <mass>0.035</mass>
366 </inertial>
367 <collision name="collision">
368 <pose>0 0.047 -0.005 0 0 0</pose>
369 <geometry>
370 <box>
371 <size>0.008 0.130 0.022</size>
372 </box>
373 </geometry>
374 </collision>
375
376 <pose>0.069 -0.047 0.107 0 0 0</pose>
377
378 <sensor name="intel_realsense_r200_depth" type="depth">
379 <always_on>1</always_on>
380 <update_rate>5</update_rate>
381 <pose>0.064 -0.047 0.107 0 0 0</pose>
382 <gz_frame_id>camera_depth_frame</gz_frame_id>
383 <camera name="realsense_depth_camera">
384 <horizontal_fov>1.047</horizontal_fov>
385 <image>
386 <width>320</width>
387 <height>240</height>
388 </image>
389 <lens>
390 <projection>
391 <!-- focal_length = fx = fy = width / ( 2 * tan (hfov / 2 ) ) -->
392 <!-- tx = hackBaseline * focal_length -->
393 <tx>19.4</tx>
394 </projection>
395 </lens>
396 <depth_camera>
397 <clip>
398 <near>0.001</near>
399 <far>5.0</far>
400 </clip>
401 </depth_camera>
402 </camera>
403 </sensor>
404 </link>
405
406 <joint name="base_joint" type="fixed">
407 <parent>base_footprint</parent>
408 <child>base_link</child>
409 <pose>0.0 0.0 0.010 0 0 0</pose>
410 </joint>
411
412 <joint name="wheel_left_joint" type="revolute">
413 <parent>base_link</parent>
414 <child>wheel_left_link</child>
415 <pose>0.0 0.144 0.023 -1.57 0 0</pose>
416 <axis>
417 <xyz>0 0 1</xyz>
418 </axis>
419 </joint>
420
421 <joint name="wheel_right_joint" type="revolute">
422 <parent>base_link</parent>
423 <child>wheel_right_link</child>
424 <pose>0.0 -0.144 0.023 -1.57 0 0</pose>
425 <axis>
426 <xyz>0 0 1</xyz>
427 </axis>
428 </joint>
429
430 <joint name='caster_back_right_joint' type='ball'>
431 <parent>base_link</parent>
432 <child>caster_back_right_link</child>
433 </joint>
434
435 <joint name='caster_back_left_joint' type='ball'>
436 <parent>base_link</parent>
437 <child>caster_back_left_link</child>
438 </joint>
439
440 <joint name="lidar_joint" type="fixed">
441 <parent>base_link</parent>
442 <child>base_scan</child>
443 <pose>-0.064 0 0.121 0 0 0</pose>
444 <axis>
445 <xyz>0 0 1</xyz>
446 </axis>
447 </joint>
448
449 <joint name="camera_joint" type="fixed">
450 <parent>base_link</parent>
451 <child>camera_link</child>
452 <pose>0.064 -0.065 0.094 0 0 0</pose>
453 <axis>
454 <xyz>0 0 1</xyz>
455 </axis>
456 </joint>
457
458 <joint name="imu_joint" type="fixed">
459 <parent>base_link</parent>
460 <child>imu_link</child>
461 <pose>0.0 0 0.068 0 0 0</pose>
462 </joint>
463
464
465 <plugin
466 filename="gz-sim-diff-drive-system"
467 name="gz::sim::systems::DiffDrive">
468 <left_joint>wheel_left_joint</left_joint>
469 <right_joint>wheel_right_joint</right_joint>
470 <wheel_separation>0.287</wheel_separation>
471 <wheel_radius>0.033</wheel_radius>
472 <max_linear_acceleration>1</max_linear_acceleration>
473 <min_linear_acceleration>-1</min_linear_acceleration>
474 <max_angular_acceleration>2</max_angular_acceleration>
475 <min_angular_acceleration>-2</min_angular_acceleration>
476 <max_linear_velocity>0.46</max_linear_velocity>
477 <min_linear_velocity>-0.46</min_linear_velocity>
478 <max_angular_velocity>1.9</max_angular_velocity>
479 <min_angular_velocity>-1.9</min_angular_velocity>
480 <topic>$(arg namespace)/cmd_vel</topic>
481 <odom_topic>$(arg namespace)/odom</odom_topic>
482 <tf_topic>$(arg namespace)/tf</tf_topic>
483 <frame_id>odom</frame_id>
484 <child_frame_id>base_footprint</child_frame_id>
485 <odom_publish_frequency>30</odom_publish_frequency>
486 </plugin>
487
488 <plugin
489 filename="gz-sim-joint-state-publisher-system"
490 name="gz::sim::systems::JointStatePublisher">
491 <joint_name>wheel_left_joint</joint_name>
492 <joint_name>wheel_right_joint</joint_name>
493 <topic>$(arg namespace)/joint_states</topic>
494 <update_rate>30</update_rate>
495 </plugin>
496
497 </model>
498</sdf>
Although .sdf files are meant to replace, or supersede, .urdf files in the long term, the process is
still ongoing or under discussion. It is likely that you will see .urdf files heavily used in many ROS2
packages. Nonetheless, .urdf are close in syntax to .sdf files and should be no surprise.
The contents of turtlebot3_waffle.urdf.
1<?xml version="1.0" ?>
2<robot name="turtlebot3_waffle" xmlns:xacro="http://ros.org/wiki/xacro">
3 <!-- <xacro:include filename="$(find turtlebot3_description)/urdf/common_properties.urdf"/>
4
5 <xacro:property name="r200_cam_rgb_px" value="0.005"/>
6 <xacro:property name="r200_cam_rgb_py" value="0.018"/>
7 <xacro:property name="r200_cam_rgb_pz" value="0.013"/>
8 <xacro:property name="r200_cam_depth_offset" value="0.01"/> -->
9
10 <!-- Init colour -->
11 <material name="black">
12 <color rgba="0.0 0.0 0.0 1.0"/>
13 </material>
14
15 <material name="dark">
16 <color rgba="0.3 0.3 0.3 1.0"/>
17 </material>
18
19 <material name="light_black">
20 <color rgba="0.4 0.4 0.4 1.0"/>
21 </material>
22
23 <material name="blue">
24 <color rgba="0.0 0.0 0.8 1.0"/>
25 </material>
26
27 <material name="green">
28 <color rgba="0.0 0.8 0.0 1.0"/>
29 </material>
30
31 <material name="grey">
32 <color rgba="0.5 0.5 0.5 1.0"/>
33 </material>
34
35 <material name="orange">
36 <color rgba="1.0 0.4235 0.0392 1.0"/>
37 </material>
38
39 <material name="brown">
40 <color rgba="0.8706 0.8118 0.7647 1.0"/>
41 </material>
42
43 <material name="red">
44 <color rgba="0.8 0.0 0.0 1.0"/>
45 </material>
46
47 <material name="white">
48 <color rgba="1.0 1.0 1.0 1.0"/>
49 </material>
50
51 <link name="base_footprint"/>
52
53 <joint name="base_joint" type="fixed">
54 <parent link="base_footprint"/>
55 <child link="base_link" />
56 <origin xyz="0 0 0.010" rpy="0 0 0"/>
57 </joint>
58
59 <link name="base_link">
60 <visual>
61 <origin xyz="-0.064 0 0.0" rpy="0 0 0"/>
62 <geometry>
63 <mesh filename="package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/waffle_base.dae" scale="0.001 0.001 0.001"/>
64 </geometry>
65 <material name="light_black"/>
66 </visual>
67
68 <collision>
69 <origin xyz="-0.064 0 0.047" rpy="0 0 0"/>
70 <geometry>
71 <box size="0.266 0.266 0.094"/>
72 </geometry>
73 </collision>
74
75 <inertial>
76 <origin xyz="0 0 0" rpy="0 0 0"/>
77 <mass value="1.3729096e+00"/>
78 <inertia ixx="8.7002718e-03" ixy="-4.7576583e-05" ixz="1.1160499e-04"
79 iyy="8.6195418e-03" iyz="-3.5422299e-06"
80 izz="1.4612727e-02" />
81 </inertial>
82 </link>
83
84 <joint name="wheel_left_joint" type="continuous">
85 <parent link="base_link"/>
86 <child link="wheel_left_link"/>
87 <origin xyz="0.0 0.144 0.023" rpy="-1.57 0 0"/>
88 <axis xyz="0 0 1"/>
89 </joint>
90
91 <link name="wheel_left_link">
92 <visual>
93 <origin xyz="0 0 0" rpy="1.57 0 0"/>
94 <geometry>
95 <mesh filename="package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/tire.dae" scale="0.001 0.001 0.001"/>
96 </geometry>
97 <material name="dark"/>
98 </visual>
99
100 <collision>
101 <origin xyz="0 0 0" rpy="0 0 0"/>
102 <geometry>
103 <cylinder length="0.018" radius="0.033"/>
104 </geometry>
105 </collision>
106
107 <inertial>
108 <origin xyz="0 0 0" />
109 <mass value="2.8498940e-02" />
110 <inertia ixx="1.1175580e-05" ixy="-4.2369783e-11" ixz="-5.9381719e-09"
111 iyy="1.1192413e-05" iyz="-1.4400107e-11"
112 izz="2.0712558e-05" />
113 </inertial>
114 </link>
115
116 <joint name="wheel_right_joint" type="continuous">
117 <parent link="base_link"/>
118 <child link="wheel_right_link"/>
119 <origin xyz="0.0 -0.144 0.023" rpy="-1.57 0 0"/>
120 <axis xyz="0 0 1"/>
121 </joint>
122
123 <link name="wheel_right_link">
124 <visual>
125 <origin xyz="0 0 0" rpy="1.57 0 0"/>
126 <geometry>
127 <mesh filename="package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/tire.dae" scale="0.001 0.001 0.001"/>
128 </geometry>
129 <material name="dark"/>
130 </visual>
131
132 <collision>
133 <origin xyz="0 0 0" rpy="0 0 0"/>
134 <geometry>
135 <cylinder length="0.018" radius="0.033"/>
136 </geometry>
137 </collision>
138
139 <inertial>
140 <origin xyz="0 0 0" />
141 <mass value="2.8498940e-02" />
142 <inertia ixx="1.1175580e-05" ixy="-4.2369783e-11" ixz="-5.9381719e-09"
143 iyy="1.1192413e-05" iyz="-1.4400107e-11"
144 izz="2.0712558e-05" />
145 </inertial>
146 </link>
147
148 <joint name="caster_back_right_joint" type="fixed">
149 <parent link="base_link"/>
150 <child link="caster_back_right_link"/>
151 <origin xyz="-0.177 -0.064 -0.004" rpy="-1.57 0 0"/>
152 </joint>
153
154 <link name="caster_back_right_link">
155 <collision>
156 <origin xyz="0 0.001 0" rpy="0 0 0"/>
157 <geometry>
158 <box size="0.030 0.009 0.020"/>
159 </geometry>
160 </collision>
161
162 <inertial>
163 <origin xyz="0 0 0" />
164 <mass value="0.005" />
165 <inertia ixx="0.001" ixy="0.0" ixz="0.0"
166 iyy="0.001" iyz="0.0"
167 izz="0.001" />
168 </inertial>
169 </link>
170
171 <joint name="caster_back_left_joint" type="fixed">
172 <parent link="base_link"/>
173 <child link="caster_back_left_link"/>
174 <origin xyz="-0.177 0.064 -0.004" rpy="-1.57 0 0"/>
175 </joint>
176
177 <link name="caster_back_left_link">
178 <collision>
179 <origin xyz="0 0.001 0" rpy="0 0 0"/>
180 <geometry>
181 <box size="0.030 0.009 0.020"/>
182 </geometry>
183 </collision>
184
185 <inertial>
186 <origin xyz="0 0 0" />
187 <mass value="0.005" />
188 <inertia ixx="0.001" ixy="0.0" ixz="0.0"
189 iyy="0.001" iyz="0.0"
190 izz="0.001" />
191 </inertial>
192 </link>
193
194 <joint name="imu_joint" type="fixed">
195 <parent link="base_link"/>
196 <child link="imu_link"/>
197 <origin xyz="0.0 0 0.068" rpy="0 0 0"/>
198 </joint>
199
200 <link name="imu_link"/>
201
202 <joint name="scan_joint" type="fixed">
203 <parent link="base_link"/>
204 <child link="base_scan"/>
205 <origin xyz="-0.064 0 0.122" rpy="0 0 0"/>
206 </joint>
207
208 <link name="base_scan">
209 <visual>
210 <origin xyz="0 0 0" rpy="0 0 0"/>
211 <geometry>
212 <mesh filename="package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/lds.dae" scale="0.001 0.001 0.001"/>
213 </geometry>
214 <material name="dark"/>
215 </visual>
216
217 <collision>
218 <origin xyz="0.015 0 -0.0065" rpy="0 0 0"/>
219 <geometry>
220 <cylinder length="0.0315" radius="0.055"/>
221 </geometry>
222 </collision>
223
224 <inertial>
225 <mass value="0.114" />
226 <origin xyz="0 0 0" />
227 <inertia ixx="0.001" ixy="0.0" ixz="0.0"
228 iyy="0.001" iyz="0.0"
229 izz="0.001" />
230 </inertial>
231 </link>
232
233 <joint name="camera_joint" type="fixed">
234 <origin xyz="0.064 -0.065 0.094" rpy="0 0 0"/>
235 <parent link="base_link"/>
236 <child link="camera_link"/>
237 </joint>
238
239 <link name="camera_link">
240 <visual>
241 <origin xyz="0 0 0" rpy="1.57 0 1.57"/>
242 <geometry>
243 <mesh filename="package://nav2_minimal_tb3_sim/models/turtlebot3_model/meshes/r200.dae" />
244 </geometry>
245 </visual>
246 <collision>
247 <origin xyz="0.003 0.065 0.007" rpy="0 0 0"/>
248 <geometry>
249 <box size="0.012 0.132 0.020"/>
250 </geometry>
251 </collision>
252
253 <!-- This inertial field needs doesn't contain reliable data!! -->
254<!-- <inertial>
255 <mass value="0.564" />
256 <origin xyz="0 0 0" />
257 <inertia ixx="0.003881243" ixy="0.0" ixz="0.0"
258 iyy="0.000498940" iyz="0.0"
259 izz="0.003879257" />
260 </inertial>-->
261 </link>
262
263 <joint name="camera_rgb_joint" type="fixed">
264 <origin xyz="0.005 0.018 0.013" rpy="0 0 0"/>
265 <!-- <origin xyz="${r200_cam_rgb_px} ${r200_cam_rgb_py} ${r200_cam_rgb_pz}" rpy="0 0 0"/> -->
266 <parent link="camera_link"/>
267 <child link="camera_rgb_frame"/>
268 </joint>
269 <link name="camera_rgb_frame"/>
270
271 <joint name="camera_rgb_optical_joint" type="fixed">
272 <origin xyz="0 0 0" rpy="-1.57 0 -1.57"/>
273 <parent link="camera_rgb_frame"/>
274 <child link="camera_rgb_optical_frame"/>
275 </joint>
276 <link name="camera_rgb_optical_frame"/>
277
278 <joint name="camera_depth_joint" type="fixed">
279 <origin xyz="0.005 0.028 0.013" rpy="-1.57 0 -1.57"/>
280 <!-- <origin xyz="${r200_cam_rgb_px} ${r200_cam_rgb_py + r200_cam_depth_offset} ${r200_cam_rgb_pz}" rpy="0 0 0"/> -->
281 <parent link="camera_link"/>
282 <child link="camera_depth_frame"/>
283 </joint>
284 <link name="camera_depth_frame"/>
285
286 <joint name="camera_depth_optical_joint" type="fixed">
287 <origin xyz="0 0 0" rpy="0 0 0"/>
288 <parent link="camera_depth_frame"/>
289 <child link="camera_depth_optical_frame"/>
290 </joint>
291 <link name="camera_depth_optical_frame"/>
292
293</robot>
So what?#
After looking through this example you have probably noticed the complexity involved into setting up nav2 for even
relatively simple projects. Having a highly configurable system increases the number of possibilities, which is great
when applying it to other settings, but might be daunting at first.
Here are some examples of what you could modify in this example and what would be the estimated difficulty of doing so.
Changing the map. Not as trivial as it might sound, because the map needs to match the Gazebo simulation scene. Some community tools might help with that.
Changing the Gazebo scene. Same problem as above, although you might benefit from SLAM, it is not likely to create a perfect map.
Changing the robot. Online tutorials and official repositories might say that it’s a matter of “changing the
urdffile”. Unfortunately, this might mean lots of other things will change, including the navigation parameters depending, for instance, on sensors and footprint of your robot.Changing planners or controllers in
nav2. That is going to be easier generally, but the parameters of one planner or controller might not map exactly to another one. It might take some tinkering to get it to work.
In conclusion, using this section, you have learned the basics of nav2 from an official example. We went through
the importance of each file and now you should have a conceptual knowledge beyond the basics for this navigation stack.
Navigation with SLAM#
See also
Official documentation: https://docs.nav2.org/tutorials/docs/navigation2_with_slam.html#navigation2-with-slam
In the previous example, we looked into navigating through a completely known map. This is a common problem to solve in robotics, but definitely not the only issue. Commonly, we have partial or no knowledge of the map through which a robot has to traverse. This is where SLAM kicks in. It becomes a problem of navigating through an incomplete and dynamic map.
In this example, they utilise the slam toolbox. Going into the details of SLAM is beyond the scope of this tutorial, but there are some interesting aspects to learn from this example even with the basic concepts of navigation.
We can run the demo with SLAM using the code below. Please note that the only difference is the flag slam:=True.
ros2 launch nav2_bringup \
tb3_simulation_launch.py \
use_sim_time:=True \
headless:=False \
sigterm_timeout:=120 \
slam:=True
You can behold my incredible map completion skills in the video below. As you will notice it will start empty and be completed as we move around it. Using our knowledge of the environment we can know where to go to complete the map and have good coverage. In practice, this can be challenging when the environment is completely unknown and has more complex topology.
Running the same demonstration a second time, we can see how to save an incomplete map. The map will be saved in the
folder in which you executed ros2 launch.
The maps will be saved into two files, compliant with nav2 use. The first one we saved will be the .yaml file.
1image: incomplete.pgm
2mode: trinary
3resolution: 0.050
4origin: [-0.934, -2.059, 0]
5negate: 0
6occupied_thresh: 0.65
7free_thresh: 0.196
The second one will be the map. When opened in a image viewer, this is how it looks like, showing its incomplete state.
This example is illustrative of the capacity to create maps using SLAM. This might not look like much, at first glance. However, please take this time to reflect that this mapping is happening with simulated data coming from Gazebo. Therefore, it is as representative of reality as the programs we have used so far allow us to be at this point.