Robot Operating System: Practical Guide for Migrating from ROS1 to ROS2
Tags: robots, ros
Published: 2021-12-20 07:04:08

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](https://admantium.com/blog/ros06_migrating_urdf_to_ros2/)_.

## 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](https://navigation.ros.org/setup_guides/urdf/setup_urdf.html) 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.

```bash
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](https://index.ros.org/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](https://index.ros.org/p/rviz2/#foxy): Contains the RViz simulation tool
- [urdf](https://index.ros.org/p/urdf/#foxy): C++ parser for the URDF file format
- [joint_state_publisher](https://index.ros.org/p/joint_state_publisher/#foxy): 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](https://index.ros.org/p/robot_state_publisher/#foxy): This package reads the `robot_description` parameter and the `joint_states` published by the aforementioned package to calculate a 3D pose estimation of the robot. The messages are published as `tf2` messages, which allow all other ROS nodes to coherently access all coordinate frames (world, links of your robot) over time.
- [controller_manager](https://index.ros.org/p/controller_manager/#foxy): 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](https://index.ros.org/p/xacro/#foxy): 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](http://wiki.ros.org/catkin/package.xml), you should use `<depend>` tags for all dependencies. For completeness, here is the complete file.

```xml
<?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.

```bash
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:

```xml
<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 name `robot_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 the `joint_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.

```xml
<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.

```python

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:

```python
def generate_launch_description():
  return launch.LaunchDescription([
    robot_state_publisher_node
  ])
```

These are the essential steps. For completeness, here is the complete launch file:

```python
import launch
from launch.substitutions import Command, LaunchConfiguration
import launch_ros
import os
from ament_index_python.packages import get_package_share_directory

def 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.

```python
import os
from glob import glob
from setuptools import setup

package_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.

```yaml
$> 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`.

```yaml
$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.

![ros06_launch_rviz.png](https://admantium.com/images/blog/ros06_launch_rviz.png)

## 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.