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.

  1. Build a ROS package using ament_python

  2. Start a simulation of a robot in an environment

  3. Start SLAM to begin mapping

  4. Drive the robot around

  5. 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):

map server saver options

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.

Generated ROS Map