Week 09 - Saving a Map
The key deliverable of this week’s assessment is to deliver a map, generated by a robot traversing an environment using SLAM. There are a few steps we must complete in order to get there.
Build a ROS package using ament_python
Start a simulation of a robot in an environment
Start SLAM to begin mapping
Drive the robot around
Use the map server to save an image and corresponding yaml file
In this guide we will use map_maker_pkg
as the package name, but this should be substituted with your ROS package name. We will being making use of the gz_example_description_package
, you can download a copy here: gz_example_robot_description.zip
Step 1 - Make a Package
This package can be used both for the submission (which requires a ROS package), but also to hold a luanch file which will make running all this easier.
Note
Why bother with a launch file at all? Bring on lots of terminal sessions!
The biggest convenience of the launch file is being able to declare that all the nodes should use_sim_time
, the time provided by the Gazebo simulator rather than the computer’s local clock.
This means that laser scan messages and transformations in time can be performed correctly. If nodes are running on different clocks, things very quickly fall down.
Make a package the usual way:
cd ~/MY_ROS_WS/src/
ros2 pkg create map_maker_pkg --build-type ament_python
cd map_maker_pkg
mkdir launch
mkdir maps
Your setup.py file should have some extra lines added to include the launch and map directories:
1from setuptools import setup
2import os
3from glob import glob
4
5package_name = 'map_maker_pkg'
6
7setup(
8 name=package_name,
9 version='0.0.0',
10 packages=[package_name],
11 data_files=[
12 ('share/ament_index/resource_index/packages',
13 ['resource/' + package_name]),
14 ('share/' + package_name, ['package.xml']),
15 (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
16 (os.path.join('share', package_name, 'maps'), glob(os.path.join('maps', '*.[py][ga]*'))),
17 ],
18 install_requires=['setuptools'],
19 zip_safe=True,
20 maintainer='murilo',
21 maintainer_email='your@email.org',
22 description='TODO: Package description',
23 license='TODO: License declaration',
24 tests_require=['pytest'],
25 entry_points={
26 'console_scripts': [
27 ],
28 },
29)
cd ~/MY_ROS_WS/
colcon build
source install/setup.bash
Step 2 - Start a Simulation
A simulation environment has been provided for you as part of the synchronous tutorial material, namely gz_example_robot_description
,
which includes a launch file sim_robot.launch.py
.
If we wanted to use a joystick/gamepad for teleoperation, then here would be a good place to put a additional node to run. If you are using the teleop_twist_keyboard
package this should not be included in the launch file, as you will need to read keystrokes this does not behave as well with a launch file. Its easier to have this in a separate terminal.
A simple launch file called map_maker.launch.py
should look something like the file below.
1from ament_index_python.packages import get_package_share_directory
2from launch import LaunchDescription
3from launch.actions import IncludeLaunchDescription
4from launch_ros.actions import SetParameter
5from launch.launch_description_sources import PythonLaunchDescriptionSource
6
7
8def generate_launch_description():
9 ld = LaunchDescription()
10
11
12 # Include the Gazebo launch file
13 launch_gazebo = IncludeLaunchDescription(
14 PythonLaunchDescriptionSource([get_package_share_directory('gz_example_robot_description'), '/launch', '/sim_robot.launch.py']),
15 launch_arguments={}.items(),
16 )
17
18
19 # Add actions to LaunchDescription
20 ld.add_action(SetParameter(name='use_sim_time', value=True))
21 ld.add_action(launch_gazebo)
22
23 return ld
All it does is call the launch file sim_robot.launch.py
from the package gz_example_robot_description
, we will add another launch file to it next.
As a sanity check, colcon build
, followed by source install/setup.bash
and check the launch file runs correctly using
ros2 launch map_maker_pkg map_maker.launch.py
You can then Ctrl+C
to stop the simulation and RVIZ.
Step 3 - Start SLAM
This once again is trivial. By simply adding an additional launch file action to our launch file, SLAM toolbox can be running to provide Simultaneous Localisation and Mapping (SLAM). Our launch file should look something like the one below.
1from ament_index_python.packages import get_package_share_directory
2from launch import LaunchDescription
3from launch.actions import IncludeLaunchDescription
4from launch_ros.actions import SetParameter
5from launch.launch_description_sources import PythonLaunchDescriptionSource
6
7
8def generate_launch_description():
9 ld = LaunchDescription()
10
11
12 # Include the Gazebo launch file
13 launch_gazebo = IncludeLaunchDescription(
14 PythonLaunchDescriptionSource([get_package_share_directory('gz_example_robot_description'), '/launch', '/sim_robot.launch.py']),
15 launch_arguments={}.items(),
16 )
17
18 # Include SLAM Toolbox standard launch file
19 launch_slamtoolbox = IncludeLaunchDescription(
20 PythonLaunchDescriptionSource([get_package_share_directory('slam_toolbox'), '/launch', '/online_async_launch.py']),
21 launch_arguments={}.items(),
22 )
23
24
25 # Add actions to LaunchDescription
26 ld.add_action(SetParameter(name='use_sim_time', value=True))
27 ld.add_action(launch_gazebo)
28 ld.add_action(launch_slamtoolbox)
29
30 return ld
As always, colcon build
, source install/setup.bash
and check the launch file works correctly.
You probably want to see the map in RVIZ right?
Include the map via :menuselection: Add > By topic > /map. You then need to adjust the Quality of Service (QoS) settings to match that of the /map
topic. The easiest way is to open a new terminal and run
ros2 topic info --verbose /map
where the --verbose
option will list the expected QoS settings. Be careful - look for the output which has endpoint type: PUBLISHER
.
Note
If you want to know more about Quality of Service in ROS2, check out some documentation here , and a video from the 2019 ROSCon .
It is recommended to include things like the laser scan and the robot model to the RVIZ visualisation to get a better sense of what is going on.
Step 4 - Drive the Robot Around
If you haven’t already inclued a joystick teleop node, then use the keyboard teleop node in a separate terminal using ros2 run teleop_twist_keyboard teleop_twist_keyboard
.
By manually driving the robot around, we can fill in the map of the environment.
Step 5 - Saving the Map
We could add this portion to our launch file, but at this stage it would be unnecessary, instead we will run everything from a new terminal. Start by launching the map_server map_saver_launch via
ros2 launch nav2_map_server map_saver_server.launch.py
This will have spawned new ROS services we can call to save our map, namely /map_saver/save_map
, of the type nav2_msgs/srv/SaveMap
. There are a few options we need to set during the service call (listed in detail here):
Option |
Value |
Notes |
---|---|---|
map_topic |
map |
The default topic is usually map |
map_url |
map_name |
It will also accept a full path, such as /home/username/Documents/map_name |
image_format |
pgm |
pgm (greyscale image) is the default |
map_mode |
trinary |
Records three possible states (Unknown, Occupied, Free Space) as three colour/greyscale values |
free_thresh |
0.25 |
Probabilities equal or below are treated as free space (robot can move through it) |
occupied_thresh |
0.65 |
Probabilities equal or above are treated as occupied (there is an obstacle) |
In yet another new terminal, either use rqt (selecting the service caller plugin and finding the correct service) or the command line to call the service.
ros2 service call /map_saver/save_map nav2_msgs/srv/SaveMap "map_topic: 'map'
map_url: 'map_name'
image_format: 'pgm'
map_mode: 'trinary'
free_thresh: 0.25
occupied_thresh: 0.65"
Hint
Instead of manually typing out the entire message, you can start with "
and use tab-completion to fill in the rest of the message with default values, then simply change the values to desired.
Tada! If the service request was successful, you should have a map called map_name.pgm
with an associated metadata file map_name.yaml
. Ensure these are held in your package maps
directory for submission.