ROS1 vs ROS2, Practical Overview For ROS Developers

You may be wondering what are the differences between ROS1 and ROS2. And what does it change for you? ROS1 vs ROS2, let’s begin!

In this article I’ll give you a practical overview of what has changed, and what’s new. I’ll try to be brief. Not too many details, not too much theory, but a focus on what affects you, as a developer.

If you’re new to ROS (ROS1 or ROS2) it’s still a good thing to read this, because it can help you understand some parts of the ROS system.

To be clear and not bring any confusion, I will use the term “ROS” when talking about ROS – aka Robot Operating System – in general: the ecosystem, the philosophy behind ROS, the community, etc. I will use “ROS1” and “ROS2” when talking about the specific ROS versions.

Why ROS2 and not keep ROS1

ROS1, initially created in 2007 by Willow Garage, has become huge among the open source robotics community.

The team behind ROS1 has learned – with all those years of experience, what important features are missing, and what could be improved. Unfortunately, adding all those modifications into ROS1 would have required many breaking changes, and make ROS1 quite unstable. So, ROS2 was developed from scratch, and is a complete new ROS.

As for now ROS is not very popular in the industry, and lacks some of the most important requirements, such as real-time, safety, certification, security. One of the goals for ROS2 is to make it compatible with industrial applications.

ROS1 and ROS2 distributions

Here’s the situation with ROS1: ROS Noetic (release date: 2020) is the last ROS1 version. This final ROS1 version main’s goal is to provide Python3 support for developers/organizations who need to continue working with ROS1 for a while.

ROS Noetic’s EOL (End of Life) is scheduled for 2025. After that, no more ROS1! So, if you have a big code base in ROS1 today, it’s totally OK, but don’t wait until 2024 to start making changes.

For ROS2, from the LTS (Long Term Support) version Foxy Fitzroy (release date: 2020), a new ROS2 version is released every year. Same as what was previously done for ROS1.

You can already start working with ROS2 which is pretty stable now – at least the core functionalities, some 3rd party plugins are still missing.

Now let’s explore the differences. I’ve separated them into 3 main parts in order to have some sort of a structure, however feel free to jump to any point, they can all be read independently.

ROS1 vs ROS2: writing your nodes

The ROS API – rclcpp, rclpy

In ROS1, for Cpp you use roscpp, and for Python, rospy. Both libraries are completely independent and built from scratch. It means that the API is not necessarily the same between roscpp and rospy, and some features are developed for one, and not the other.

ROS2 has more layers. There is only one base library, named rcl, and implemented in C. This is the foundation which contains all of the ROS2 core features.

You won’t use the rcl library directly in your programs. You’ll use another client library built on top of rcl. For example: rclcpp for Cpp, rclpy for Python.

What’s great about it? Well any new functionality only needs to be implemented with rcl. Then, the client libraries on top of rcl just need to provide the binding.

For you, as a developer, it means that:

  • The API between rclcpp and rclpy will be much more similar than the API between roscpp and rospy.
  • It will be easier to create and use other language client libraries, for example rclnodejs, rcljava, etc. No need to reinvent the wheel, you just need to make a C binding with rcl. And all clients in all languages will have a similar API.
  • When a new core feature is released, it will be available sooner in different languages, so you won’t have to wait too much.

Python and Cpp versions

As you may know, Python2 is not supported anymore. Well in fact, to provide a smoother transition, it’s still supported for Ubuntu 18 and ROS1 Melodic until their EOL (2023).

ROS1 Noetic targets Python3, as well as all ROS2 versions.

Now, for Cpp, there is some great progress. ROS1 was targeting Cpp 98, and you could use Cpp 11/14 in later ROS1 versions, provided that it didn’t break other dependencies.

In ROS2 you can now use Cpp 11 and 14 by default. Cpp 17 is also on the roadmap. That’s great because new versions of Cpp introduce many useful functionalities, making development easier, quicker, and safer. Also, it makes Cpp more fun, and maybe this will help democratize this powerful and great language (well it seems I’m biased).

Writing a node (with OOP)

In ROS1 there is no specific structure telling you how you should write your node functionalities. You can decide to add callback functions anywhere in your program, or use OOP if you wish to, but every one’s implementation could be unique.

In ROS2 things are different. There is a convention about how to write your nodes. You have to create a class which inherits from the Node object (for example: rclcpp::Node in Cpp, rclpy.node.Node in Python). In this class you’ll have all your ROS2 functionalities.

This is great because it will save everyone a lot of time. You already have a good, modular structure for writing your node. It will make your programs cleaner, and cooperation between developers on different projects will be easier.

Check out how to write a minimal ROS2 Python node, and a ROS2 Cpp node, with OOP.

Using OOP for your nodes in ROS2 also allows you to convert them to components, which is a new feature in ROS2. Let’s see that now.

Multiple nodes in the same executable – ROS2 Components

In ROS1 a node is tight to an executable. A new functionality, named Nodelets, was added in ROS1 to be able to write multiple nodes in the same executable, with intra-process communication. This is really great when you have limited hardware resources and/or you need to send a lot of messages between nodes.

In ROS2, Nodelets are not called Nodelets anymore. The functionality has been directly included in the ROS2 core, and is now called “components”.

So, with ROS2, you can handle many nodes from the same executable, using components. A component is simply a slightly modified node class (we’re still using OOP there).

Then, you can start your components from a launch file, the terminal, or from an executable. And you can activate intra-process communication to remove any ROS2 communication overhead.

Building components is a good practice to create efficient ROS2 applications.

Lifecycled nodes

ROS2 introduces the concept of lifecycled nodes. A lifecycled node has different states: unconfigured, inactive, active, finalized. This is very useful when you need a setup phase before actually running your node’s main functionalities.

When you start such a node it is initially unconfigured. Through the provided interface (ROS2 services), you can ask for a transition to another state. When you do that a predefined callback will be triggered inside the node.

Let’s say you have a node for a sensor. You first need to make sure the sensor is detected, and the communication has been successfully started. Then you can start your reading loop and publish the data.

With a lifecycled node you can clearly separate this: first you allocate memory for publishers, subscribers, and other instantiated objects. Then, you initiate the communication with the sensor. And finally you run your reading loop to publish the data.

Writing launch files

Launch files allow you to start all your nodes from one file. You can start a standard node, a component, a lifecycled node. You can add arguments, parameters, and many other options.

In ROS1, you’ve been used to write launch files with XML.

In ROS2 you will now use Python to write your launch files. There is an API allowing you to start nodes, retrieve config files, add parameters, etc. And it will allow you to customize your launch files much more than before.

However, is writing a launch file in Python really new? Well in fact no. In ROS1 there is also a Python API. The problem is: no one is aware of it, and there’s almost zero documentation about it. So, no one uses it. And it became quite the norm to write launch files in XML, which is great, but certainly not as modular as with the Python API.

And… You can also write your ROS2 launch files with XML if you want to. But prefer using Python, as it brings more modularity, is more documented, and has become the ROS2 convention for launch files.

ROS1 vs ROS2: Communication

No more ROS master

One thing you’ve learned with ROS1: always start a ROS master before you run a node. The ROS1 master will act as a DNS server for your nodes, so they can retrieve each other.

In ROS2, no more ROS master! This is no more a centralized system. Each node has the capacity to discover other nodes. You can simply start a node without having to worry if you have a master running or not.

This change is great because it allows you to create a fully distributed system. Each node is independent and not tight to a global master.

When creating a multi-machine ROS2 application, you won’t have to define one machine as the “master”. Each machine will be independent and able to start on its own, connect and disconnect with each other, with less setup than in ROS1.

Parameters

So, in ROS1, parameters are handled by the parameter server, which is itself handled by… The ROS master.

In ROS2, no more ROS master = no more (global) parameter server.

The concept of parameters has been completely changed. There is no global parameter anymore. Each parameter is specific to a node.

A node declares and manages its own parameters, and those parameters are destroyed when the node is killed.

It’s like each node has its own parameter server. When you start a node a few ROS2 services are created. Those allow you to interact with its parameters from the terminal or from other nodes.

In addition, you can easily modify a node’s parameters after they’ve been created, using a parameter callback.

If you were using the dynamic_reconfigure tool in ROS1, well, good news, now this is part of the core functionalities. No more extra config, all you need is to bind a parameter callback to your node.

Services

In ROS1, services are synchronous. When your service client asks a request to the server, it is stuck until the server responds (or fails).

In ROS2, services are asynchronous.

When you call a service, you can add a callback function which will be triggered when the server responds. In the meantime your main thread is not stuck.

And of course, if you want you can also use services synchronously.

Actions

In ROS1, actions were never in the core functionalities. It was an addition made after a few years, to solve the problem that services were not asynchronous, and did not have a feedback or cancelation mechanism.

So, actions in ROS1 are entirely built on top of ROS1 topics.

In ROS2, actions are now part of the ROS2 core. The API for Cpp and Python is quite similar as for ROS1, so no problem with the code.

Underneath, actions still use topics for feedback and goal status, but also (asynchronous) services for setting a goal, canceling a goal, and requesting a result.

And now, actions also have their own command line tool! As you would do with a service, you can now send an action goal to a server, directly from the terminal.

Messages, Services, and Action definitions

The way to create definitions for messages, services, and actions is quite similar in ROS1 vs ROS2. You still put them into msg/, srv/, and action/ folders.

But after you compile them, a namespace is added:

  • Message: msg/…
  • Services: srv/…
  • Actions: action/…

For example, let’s say you have a package named my_robot_msgs, and inside this package you have created a message named Temperature, plus a service named ActivateButton. In your node’s code you’ll have to import them using:

  • my_robot_msgs/msg/Temperature.
  • my_robot_msgs/srv/ActivateButton.

This is great because it reduces the confusion, and makes the separation clearer between all 3 types of communications.

QoS

ROS2 introduces QoS, or Quality of Service.

With that feature you can choose how your nodes handle communication: do you want to make sure you receive all messages? Or is it OK to lose a few messages, as long as the data is frequently updated? Do you want to keep a queue of messages in case a node doesn’t have the time to process them all, or do you want to drop any new message if a callback for a previous message is still running?

Well, if you have to ask such questions for your application, then you’ll need to tune QoS for your nodes.

By default, the QoS for ROS2 communication (topics, services, …) has been chosen so you can expect the same behavior as in ROS1:

  • Any node subscribing to a topic won’t receive previous messages, only messages published after subscribing.
  • Like TCP, messages are guaranteed to be delivered.
  • You can set a queue size for delivered messages waiting to be processed.

If you have to deal with a lossy wireless network, and/or a large message bandwidth, QoS is a setting worth looking at.

But if you’re just getting started with ROS, or have a very simple application, don’t worry about QoS. There are more important things to learn first, and you will come back to QoS when you need it.

ROS1 vs ROS2: Packages, workspace and environment

Building your nodes

The build system in ROS1 is catkin. You use “catkin_make” or “catkin build” in order to build and install your packages.

In ROS2, no more catkin. Ament is the new building system, and on top of that you get the colcon command line tool.

To compile, you’ll use the command “colcon build” in your ROS2 workspace.

There is much more to say about ament and colcon, but with just this information you’ll be able to build your first nodes without any problem.

Command line tools

Most of the command line tools are similar between ROS1 and ROS2. The name of the tools, and some options are different, but otherwise there is no big difference when you use them.

For example, to list all topics, in ROS1 you’d do “rostopic list”, and in ROS2 “ros2 topic list”. “rosservice” becomes “ros2 service”, etc.

You just have to write “ros2”, followed by the name of the tool you want to use.

Cpp and Python packages

With ROS1, you create a package and then you add any Cpp/Python file you want.

ROS2 makes the difference between a Cpp and a Python package. When creating the package from the command line, you have to specify one build type: ament_cmake or ament_python.

Depending on that argument, the package architecture won’t be the same.

For a Cpp package, things are quite similar with ROS1. You still have a CMakeLists.txt. You just have to adapt your cmake instructions to use ament and not catkin.

For a Python package, things are different: you have some new files, such as setup.py and setup.cfg. The setup.py replaces the CMakeLists.txt. You can of course directly run your Python scripts, but if you want to start them from ROS2 command line tools or a launch file, you’ll have to install them first (with “colcon build”).

You can also, if you want to, create a ROS2 package for both Python and Cpp, but this requires a little bit more setup.

Overall, setting packages up in ROS2 is a little bit more complex than in ROS1, but it’s also more complete, and better organized.

Sourcing workspace and overlays

Sourcing your ROS environment is not so much different between ROS1 and ROS2. You first source your global ROS installation, then your workspace, and you can use your custom code.

ROS2 brings the concept of overlays. You can have multiple workspaces on top of each other.

First you source your global ROS installation, then your first workspace (overlay), your second overlay, etc. If a package has the same name in a lower level overlay and a higher level overlay, then only the higher level one will be used.

When you develop your application and already have a certain number of packages, you can create an overlay for just one package. This will allow you to quickly iterate on it, while keeping your code base unchanged for other projects.

With this technique you can also override a package which is already installed from binaries. This is very practical so you can keep the package installed, while having your own version for a specific application.

OS support

ROS1’s main target is Ubuntu.

Great news for ROS2: thanks to its new architecture, you can install and use it on Ubuntu, MacOS, and Windows 10 (+ other OSes, but those are the 3 main ones).

This will make ROS2 more accessible and more embeddable in many applications.

For example, you could have a mobile robot with Raspberry Pi and Ubuntu, and another computer using Windows for a 3D simulation tool and a driver node for a camera scanning the scene. And all of that running smoothly together.

When to switch from ROS1 to ROS2?

Well, it’s not as simple as that, and many people will tell you different answers. ROS1 is still strong, with many stable plugins, more documentation and 3rd party plugins. Eventually it will end, but you still have a few years before that.

If you’re new to ROS (whether ROS1 or ROS2), then you should probably learn ROS2 fundamentals. But then it can also be interesting to get a taste of ROS1 too. Why? Because you may understand some things in ROS2 better if you also see how it’s done in ROS1. And also, some tools/packages you want to use may not yet be ported to ROS2, so you’ll have no choice but to use the ROS1 version of the package. In this case the ros1_bridge package will be useful (see next section).

If you already know ROS and want to start a brand new project, then going the ROS2 way is probably what you should do, so it means less transition work in the future. The core concepts between ROS1 and ROS2 are similar, so the more experienced you are with ROS1, the less time you’ll take to learn ROS2. You can also use ROS1 and ROS2, hand in hand with ros1_bridge, in order to use missing tools and plugins.

If you already have a code base in ROS1 for one or more of your robots, or for a complete organization with dozens of developers, switching to ROS2 may represent a lot of work. The bigger the code base and the influence of ROS on your project, the longer it will take, and the more complex it will be. You may choose to continue working with ROS1 for legacy projects, and start working with ROS2 for new projects. Or you could start porting all your code now, knowing that there’s a lot of work involved (depending on how well your code is written). First, before you decide to make the complete switch, make sure that most of the ROS features you need have been ported to ROS2. Again, ros1_bridge may help you during the transition.

Using ROS1 and ROS2 together with the ros1_bridge package

If you need to work with an existing ROS1 code base, but want to develop new features with ROS2, then you are not necessarily stuck.

You can use the ROS2 package named ros1_bridge, which will make, as its name suggests, a bridge between ROS1 and ROS2 communications.

Even if the concepts are the same between ROS1 and ROS2, the communication underneath is not directly compatible, and some adaptation is required. The ros1_bridge package provides that.

When doing a transition to ROS2, you can start porting a few packages in ROS2, and make those packages communicate with the rest of your ROS1 application. Then, you port more and more packages until there is nothing left written with ROS1. During all the porting time, your application can still work as expected.

ROS1 vs ROS2: Conclusion

In this article you’ve seen some of the major differences between ROS1 and ROS2. I tried to make the approach focused on the practical side, so you can get an overview of what’s changing for you, when you develop a robotics application with ROS.

To stay informed, you can check the ROS Discourse forums, where you’ll see interesting discussions about new concepts, debates and announcements.

Now, the list presented here is certainly not exhaustive and some information is subject to change, because ROS2 is still evolving a lot. But it’s a good starting point to see what you need to focus on, when you decide to learn ROS2.

FREE Mini-Course

Don't miss this opportunity:

ROS2 Core Concepts in 1h


>> ROS2 Core Concepts Overview in 1h <<