Robot Operating System: Practical Guide for Migrating from ROS1 to ROS2
ROS2 is the ongoing effort to modernize the ROS ecosystem to a modern codebase. It includes several changes: Python-based build system, updated C/C++ language standard, new CLI commands and an updated architecture of nodes, topic and messages.
When I started my robots projec, I used ROS1 exclusively. Therefore, all the codebase regarding my robot — the URDF file und RVIZ launch files — were developed in ROS1. In April 2021, I finished the first moving prototype without any ROS functionality, and then in May started to add ROS to the prototype. I then switched to ROS2 so that my project uses an up-to-data code base.
This article summarizes my lessons learned when moving a robot definition and RVIZ launch file from ROS1 to ROS2.
This article originally appeared at my blog admantium.com.
Create a new ROS2 Package with the Python Build System
Let’s create a new ROS2 package that uses the Python build system, configure the dependencies, and then migrate an URDF file from ROS1 to ROS2. The following steps loosely follow the ROS2 navigation tutorial.
Create the Package
When you create a new ROS2 package, you can select between two build types. Coming from ROS1, the type ament_cmake
will create a CMake file that manages your packages build instructions. Embracing the new Python build system, I use ament_python
that will provide a central Python file instead.
ros2 pkg create --build-type ament_python radu_bot
Add Dependencies to the Package.xml
In order to have the new nodes started correctly, we need to add additional dependencies, other ROS packages, to the project. You can setup the dependencies already with the pkg create --add-dependencies
flag, but I like to add them manually,
The dependencies that we need are the following:
- rviz2: Contains the RViz simulation tool
- urdf: C++ parser for the URDF file format
- joint_state_publisher: Publishes the state of all non-static joints for an URDF-described robot. This package reads the parameter
robot_description
from the ROS parameter server to build a representation of the robot. Then, it continuously publishes messages that contain name, position, velocity and effort of each joint. - robot_state_publisher: This package reads the
robot_description
parameter and thejoint_states
published by the aforementioned package to calculate a 3D pose estimation of the robot. The messages are published astf2
messages, which allow all other ROS nodes to coherently access all coordinate frames (world, links of your robot) over time. - controller_manager: This packages allows direct access to a robot, which means that the robots hardware, is actuators and sensors, are wrapped in a common format which can be used by other ROS nodes and tools, including RViz and Gazebo.
- xacro: With XACRO, you can define XML macros to be used in creating URDF files, which drastically reduces the amount of code you need to write and enables flexible “builds” of your robot, e.g. adding tags specifically required for Gazebo.
The place to define them is the package.xml
file. As stated in the ROS documentation, you should use <depend>
tags for all dependencies. For completeness, here is the complete file.
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>radu_bot</name>
<version>0.1.0</version>
<description>Simulation of the RADU bot</description> <maintainer email="devcon@admantium.com">devcon</maintainer>
<license>UNLICENSED</license> <depend>urdf</depend> <depend>joint_state_publisher</depend>
<depend>joint_state_publisher_gui</depend>
<depend>robot_state_publisher</depend>
<depend>rviz2</depend>
<depend>xacro</depend> <test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend> <export>
<build_type>ament_python</build_type>
</export>
</package>
After adding the dependencies, run the following two commands in your ros2 workspace directory.
sudo apt-get install ros-foxy-joint-state-publisher-gui ros-foxy-xacro
rosdep update
rosdep install --from-paths src --ignore-src --rosdistro foxy -y
Migrate the Launch File
ROS2 does not support XML files anymore. Instead, you are using Python files to launch and configure ROS nodes as well as to start tools like RVIZ and Gazebo. Since they are scripts, you can build much more logic into launching instead of just declaratively listing. However, the configuration complexity is still high, e.g. you still need to launch special controller or navigation nodes when you want to spawn and move a robot inside a simulation.
The old XML launch file has the following content:
<launch>
<param name="robot_description" textfile="$(find car-robot)/urdf/bot.urdf"/>
<arg name="rvizconfig" default="$(find urdf_tutorial)/rviz/urdf.rviz" />
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher"/>
<node name="joint_state_publisher" pkg="joint_state_publisher" type="joint_state_publisher"/>
<node name="rviz" pkg="rviz" type="rviz" args="-d $(arg rvizconfig)" />
</launch>
It consists of these parts/concepts:
- The
param
declaration defines the standard parameter namerobot_description
. It refers to the URDF file that contains your robot definition. - The
arg
declaration loads the rviz configuration file, it includes the settings so that our robot will be displayed automatically. - Two control nodes, the
robot_state_publisher
and thejoint_state_publisher
are created - these nodes are interfaces between the robot and rviz - Finally, we start the
rviz
node and pass the configuration file to it.
These parts are translated into ROS2 Python following a simple rule: Each <node>
gets translated to a Python Node
object, and the <param>
and <arg>
tags are configuration parameters of a Node
.
Consider this example how the robot_description
is represented.
<param name="robot_description" textfile="$(find car-robot)/urdf/bot.urdf"/>
<node name="robot_state_publisher" pkg="robot_state_publisher" type="robot_state_publisher"/>
And in Python, this is represented by a robot_state_publisher
node.
robot_description = open(robot_description_path).read()robot_state_publisher_node = launch_ros.actions.Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}]
)
Once you have all nodes together, you wrap them inside a LaunchDescription
list:
def generate_launch_description():
return launch.LaunchDescription([
robot_state_publisher_node
])
These are the essential steps. For completeness, here is the complete launch file:
import launch
from launch.substitutions import Command, LaunchConfiguration
import launch_ros
import os
from ament_index_python.packages import get_package_share_directorydef generate_launch_description():
package_name = 'radu_bot' pkg_share = launch_ros.substitutions.FindPackageShare(package=package_name).find(package_name)
robot_description_path = os.path.join(pkg_share, 'urdf/robot.urdf')
rviz_config_path = os.path.join(pkg_share, 'config/urdf_config.rviz') robot_state_publisher_node = launch_ros.actions.Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}]
)
joint_state_publisher_node = launch_ros.actions.Node(
package='joint_state_publisher',
executable='joint_state_publisher',
name='joint_state_publisher'
)
joint_state_publisher_gui_node = launch_ros.actions.Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui'
)
rviz_node = launch_ros.actions.Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', rviz_config_path],
) return launch.LaunchDescription([
joint_state_publisher_node,
joint_state_publisher_gui_node,
robot_state_publisher_node,
rviz_node
])
Build Configuration
As stated before, I use the Python build system for my robot. Instead of a CMake
file, you use a Python setup.py
file. Here, you define various metadata about your project (name, maintainer, version), configure which files are copied to your build, and list commands and executables that can then be called with ros2 run
.
The following file is sufficient.
import os
from glob import glob
from setuptools import setuppackage_name = 'radu_bot'setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*')),
(os.path.join('share', package_name, 'urdf'), glob('urdf/*')),
(os.path.join('share', package_name, 'config'), glob('config/*')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='devcon',
maintainer_email='devcon@admantium.com',
description='Simulation of the RADU robot',
license='UNLICENSED',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
Adding the Robot Model
For the robot file, I’m using a complex set of XACRO files that will compile into RVIZ-compatible or Gazebo-compatible robot description files. If you just use RVIZ, there will be no changes. But to be working with Gazebo, you need to add additional properties for links and joints, and add new tags. This complex topic will be explored in future articles.
Building the Package
Now that our new package is completely assembled, let’s build it. In ROS2, you are using the colcon
build tool, which works with C-based and Python-based projects likewise.
Use the following command to start the build.
$> colcon build --symlink-install --event-handlers console_direct+ --packages-up-to radu_bot
When its finished, you can launch the program by using ros2 launch PACKAGE_NAME LAUNCH_SCRIPT
.
$Y ros2 launch radu_bot rviz.launch.py
[INFO] [launch]: All log files can be found below /home/devcon/.ros/log/2021-05-29-18-33-52-275784-giga-20756
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [joint_state_publisher-1]: process started with pid [20758]
[INFO] [joint_state_publisher_gui-2]: process started with pid [20760]
[INFO] [robot_state_publisher-3]: process started with pid [20762]
[INFO] [rviz2-4]: process started with pid [20764]
[robot_state_publisher-3] Parsing robot urdf xml string.
[robot_state_publisher-3] Link left_wheel_backside had 0 children
[robot_state_publisher-3] Link left_wheel_frontside had 0 children
[robot_state_publisher-3] Link right_wheel_backside had 0 children
[robot_state_publisher-3] Link right_wheel_frontside had 0 children
[robot_state_publisher-3] [INFO] [1622306032.679044506] [robot_state_publisher]: got segment base_link
[robot_state_publisher-3] [INFO] [1622306032.681243514] [robot_state_publisher]: got segment left_wheel_backside
[robot_state_publisher-3] [INFO] [1622306032.681317837] [robot_state_publisher]: got segment left_wheel_frontside
[robot_state_publisher-3] [INFO] [1622306032.681341170] [robot_state_publisher]: got segment right_wheel_backside
[robot_state_publisher-3] [INFO] [1622306032.681363438] [robot_state_publisher]: got segment right_wheel_frontside
[rviz2-4] [INFO] [1622306033.746836459] [rviz2]: Stereo is NOT SUPPORTED
[rviz2-4] [INFO] [1622306033.750165429] [rviz2]: OpenGl version: 4.6 (GLSL 4.6)
[rviz2-4] [INFO] [1622306033.857522813] [rviz2]: Stereo is NOT SUPPORTED
[joint_state_publisher-1] [INFO] [1622306034.345835955] [joint_state_publisher]: Waiting for robot_description to be published on the robot_description topic...
[rviz2-4] Parsing robot urdf xml string.
Centering
[joint_state_publisher_gui-2] [INFO] [1622306035.104035974] [joint_state_publisher_gui]: Centering
And finally, your robot is rendered.
Conclusion
ROS2 brought several changes to the ROS ecosystem. This tutorial showed you how to migrate a robot description and RVIZ simulation from ROS1 to ROS2. The major change is to embrace Python: Launch files are imperative Python scripts instead of declarative XML files. This gives you much more control and scripting power to start and run nodes. In addition, you can also use Python as your packages’ build language: In a central setup.py
, you configure which files are copied from your project to the installed version, manage dependencies, and add files that can be executed with the ros2 run
command.
The next article continues this ROS2 explanation by taking a closer look to implementing a XACRO-bases URDF representation of a robot that is compatible with both RVIZ and Gazebo simulation.