Getting started with ROS2 — Part 2

Sharad Maheshwari
schmiedeone
Published in
7 min readJan 6, 2022

Note: This post is the second part of our series (duh!). Sorry! What I mean is, checkout part 1 to get a better hang of stuff here 😃
Part 3 also available.

(ROS1 working knowledge assumed)

Aim —Understanding publisher, subscriber, service, and service client creation in ROS2.
Step 1 — Create a custom publisher-subscriber package,
Step 2 — Create a service-client package

Step 1 — Creating custom talker_listener package

Remember when we started learning ROS1 and made a custom package with a publisher and subscriber pair? One node publishes and the other constantly listens over a topic. Let’s do that again!

  1. Create our workspace called “ros2_ws”
mkdir -p ~/ros2_ws/src

2. Build the workspace

ROS1 uses catkin, but ROS2 uses colcon to build packages

cd ros2_ws
colcon build

At this point, you will see “0 packages finished” in the terminal (because there are no packages yet). Additionally, you will now have build, install and logs folders in the ros2_ws.

3. Create a new talker package (we use python)

cd ros2_ws/src
ros2 pkg create --build-type ament_python talker_listener

Similarly, we can also make a cpp package. Please look at ROS2 official guide.

This will be the output —

We have an empty package now!

4. Navigate to the code directory (where we will place our node file)

cd ros2_ws/src/talker_listener/talker_listener

Note: this directory has the same name as the package

5. Make the talker node

gedit talker_node.py

(or vscode, or vim, or whatever you like. I use vscode 😃)

Okay, we need to talk (no pun intended).

Here is some information to start understanding this code and compare it with ROS1.

I mentioned in the previous post that rclpy has APIs to use the underlying C codebase in ROS. And that’s what we see here.rclpy has APIs and classes to abstract a lot of stuff we did ourselves in ROS1. Node class abstracts node initialization, creating publishers, subscribers, services, clients.

Here, we inherit all methods and properties from Node.
— We don’t initialize the node ourselves
— We use “create_publisher” method from Node (line 11) to make a publisher
— We use create_timer method from Node (line 13) to use its timer and set a callback method. We used rate to do something like this in ROS1
— We can just spin the node and our timer will lead to the callback based on the set period.

Note: rclpy.spin is blocking

Please play with the code to understand more. In case of any issues, feel free to comment and I’ll make the post more verbose.

6. Add dependencies

Navigate back to find package.xml and add the following lines —

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>

Our package depends on rclpy and std_msgs.

7. Add an entry point

Open setup.py and add the following lines within the console_scripts bracket of the entry_points field:

entry_points={
'console_scripts': [
'talker = talker_listener.talker_node:main',
],
},

8. Check setup.cfg

setup.cfg should be set automatically to this —

[develop]
script_dir=$base/lib/talker
[install]
install_scripts=$base/lib/talker

This is simply telling setuptools to put your executables in lib, because ros2 run will look for them there.

9. Build and run

You likely already have the rclpy and std_msgs packages installed as part of your ROS 2 system. It’s good practice to run rosdep in the root of your workspace (ros2_ws) to check for missing dependencies before building.

rosdep install -i --from-path src --rosdistro galactic -y

build the workspace (from ros2_ws) —

colcon build

source the terminal(from ros2_ws) —

. install/setup.bash

Note: In ROS1, this was equivalent to —
source devel/setup.bash

10. Run the node

ros2 run talker_listener talker

If everything was done correctly, this will be our terminal —

Woot woot! Our first custom package in ROS2 is alive!

Let’s make a listener. As an exercise, please follow steps 5 through 9.

Here’s the code for listener_node.py (in the same directory as talker_node.py) —

We just need to add one more line in setup.py for this package. The new file will look like this —

entry_points={
'console_scripts': [
'talker = talker_listener.talker_node:main',
'listener = talker_listener.listener_node:main',
],
},

11. Build and run

You likely already have the rclpy and std_msgs packages installed as part of your ROS 2 system. It’s good practice to run rosdep in the root of your workspace (ros2_ws) to check for missing dependencies before building.

rosdep install -i --from-path src --rosdistro galactic -y

build the workspace (from ros2_ws) —

colcon build

source the terminal(from ros2_ws) —

. install/setup.bash

We are almost done. If the build was successful, we run both the nodes in different terminals.

Terminal 1:

ros2 run talker_listener talker

Terminal 2:

ros2 run talker_listener listener

Voila! We have written our first ROS2 package with a publisher and subscriber.

Step 2 — Creating integer addition service —

Now, let’s start fresh in the same workspace (ros2_ws) and create a package with a server node to add two integers and a client node to request this service.

1. Create a new add_integers package

cd ros2_ws/src
ros2 pkg create --build-type ament_python add_integers --dependencies rclpy example_interfaces

We use “dependencies” this time, which automatically updates package.xml (unlike the last time when we did this ourselves)

example_interfaces package has a simple service definition that we will use. It looks like this —

int64 a
int64 b
---
int64 sum

Later on, we make a custom service.

This will be the output —

We have an empty package now!

4. Write the service node —

Note: As I mentioned in the previous post, Node class abstracts a lot of things we did ouselved in ROS1 (with its constructor and APIs like create_publisher, create_subscriber, create_service,etc)
Please play with the code to understand more. In case of any issues, feel free to comment and I’ll make the post more verbose.

5. Add an entry point

Open setup.py and add the following lines within the console_scripts bracket of the entry_points field:

entry_points={
'console_scripts': [
'service = add_integers.service_node:main',
],
},

Let’s also create a service client(which sends two numbers using cmd arguments) and build both together.

6. Write the client node

Here’s the code for client_node.py (in the same directory as listener_node.py)

Let’s have a quick chat, shall we?

While we’d be able to understand most of the stuff in the client code here, there are two new ideas here (compared to ROS1)
1. Asynchronous client call — Line 20 uses an async call, which does not block this thread. If we use call(), which is synchronous, this thread will be locked until a response is received from the service, causing potential deadlocks
2. Future — Line 31 uses future, a value that will be updated when the service has done its job. We can check this value anytime after the call to see if we have a response.

Both the points above are explained beautifully here.

7. Add an entry point

Open setup.py and add the following lines within the console_scripts bracket of the entry_points field:

entry_points={
'console_scripts': [
'service = add_integers.service_node:main',
'client = add_integers.client_node:main',
],
},

8. Build and run

You likely already have the rclpy and std_msgs packages installed as part of your ROS 2 system. It’s good practice to run rosdep in the root of your workspace (ros2_ws) to check for missing dependencies before building.

rosdep install -i --from-path src --rosdistro galactic -y

build the workspace (from ros2_ws) —

colcon build --packages-select add_integers

source the terminal(from ros2_ws) —

. install/setup.bash

Run the service node (terminal 1) —

ros2 run add_integers service

Run the client node with two numbers as arguments (terminal 2) —

ros2 run add_integers client 5 7

Here’ what the result will look like —

Woot woot! Our package add_integers with a service node and a client node is up and running!

Interesting Fact: ROS1 did not require the creation of a node to request for a service. We could use client APIs without a node and it would work just fine.

Although I still need to confirm this, but we possibly do need a ROS2 node for our client. This is based on my observation that client creation APIs area a part of Node class now. Thus, we make a node.

And with this, we wrap up our post. We look at parameters and actions in our next post here!

Stay tuned (and stay awesome)!
Sharad

--

--